Social connectors
This additive module binds already-connected provider accounts to one project using a project-scoped vault API key. It does not connect accounts or change the existing Zernio marketing/account/analytics and Instagram/Messenger adapters. Global mounts, canonical schema migration allocation, and navigation belong to the parent integration.
Admission and vendor contracts
Checked against primary documentation on 2026-10-20. The shipped text-only allowlist is deliberately smaller than vendor capability. Unsupported combinations fail before dispatch.
| Provider | Admitted publishing | Admitted messaging | Comments in this module |
|---|---|---|---|
| Zernio | twitter, facebook, linkedin, threads, bluesky, reddit | facebook/Messenger, instagram | Unavailable |
| Buffer GraphQL | twitter, facebook, linkedin, threads, bluesky, mastodon | Unavailable | Unavailable |
| Unipile v1 | MESSENGER, INSTAGRAM | Unavailable |
Zernio's platform matrix documents wider publishing, DM and comment support, with platform-specific restrictions. Existing social inbox adapters continue to own Zernio webhook verification and message ingestion. Zernio webhooks use a raw-body HMAC, not this module's relay protocol.
Buffer's current GraphQL reference requires assets, needsApproval, channelId, schedulingType, and mode for createPost. This module supplies an empty asset list, needsApproval:false after GoodQA's exact-copy human approval, automatic scheduling and shareNow. A post ID means accepted by Buffer, not necessarily published. Accounts are re-enumerated in the configured organization and must not be paused, disconnected or locked. This implementation does not expose Buffer engagement/comment operations; it never treats the Buffer product's inbox as a messaging API.
Unipile's v1 create-post contract uses multipart account_id and text, and its PostCreated.post_id may be null. Null remains UNKNOWN. Post readback supplies the bound account_id query. Its result is labelled retrieved, not proof of ownership/publication beyond the persisted send receipt. Reply receipts use message_id. The chat is read first to check account ownership. v2 is a different protocol and is not automatically substituted. The account credential's DSN is operator configured; remoteScope is only a required descriptive scope label for Unipile, not an upstream scope selector.
Unipile message events include outbound echoes: matching account_info.user_id and sender.attendee_provider_id are ignored. The vendor lists Messenger and Instagram alongside other messaging providers, while this module admits only the two existing canonical social channels. Unipile webhook authentication documents custom secret headers, not per-delivery signatures. This module therefore accepts only an explicitly provisioned trusted signing relay: hex HMAC-SHA256 over timestamp + . + raw bytes, headers x-connector-timestamp and x-connector-signature, 300-second tolerance, secret at least 32 characters. Never point an unsigned vendor delivery directly at the relay route. Qualifying and provisioning that relay is a parent live-proof prerequisite. Dedupe is transactional on provider/account/message ID and survives retries and re-signing. Foreign bindings, stopped accounts, outgoing echoes, malformed payloads and future messages are ignored. CRM contact resolution, timeline writes and inbox recording use canonical services in one PG transaction.
Authority and execution
Reads use current outreach entitlement, crm:read and project viewer access. Writes require current project PM access and a human session. Binding/Stop/restart additionally require org:manage_integrations. Vault access uses canonical grants and secret-request pinning; plaintext is audited and never returned in snapshots. Bound accounts are rechecked immediately before dispatch. The creator and approver must both retain authority. API/MCP tools are read-only and use the existing authenticated internal transport; keys and user tokens cannot approve or dispatch.
Each immutable operation UUID binds account generation, operation, exact copy and conversation. Separate exact-digest approval precedes dispatch. PG advisory locking and a committed executing claim prevent duplicate sends across retries, races and process loss. Stop/restart cancels unclaimed drafts/approvals and increments generation. It cannot undo a network request already claimed. Dispatch is capped at 10 per binding per rolling hour using dispatch timestamps, including UNKNOWN outcomes. Replies require a canonical verified inbound message in the same provider/account/conversation within 24 hours. There are no automated replies.
Accepted means a provider receipt exists. Readback validates the persisted receipt and account. Ambiguous responses, transport loss, malformed success bodies, or missing receipts stay UNKNOWN. An executing/UNKNOWN operation fences new sends for that account across restart. No automatic timeout clears an unknown effect: expiring it would permit duplicates. Without a receipt, a parent-owned, human reconciliation workflow must establish the actual external result before any recovery; this module supplies no unsafe reset or resend endpoint. This recovery workflow remains an explicit integration limitation.
Parent integration
- Allocate the additive DDL in
apps/api/src/modules/social-connectors/integration.sqlthrough the canonical migration allocator/history, and include the local schema declarations in the canonical schema/snapshot. DDL is applied to the current migrated fixture in the owning test. Rollback is disabling the mounts; tables are retained. - In
apps/api/src/app.ts, importsocialConnectorRoutesfrom./modules/social-connectors/routesandconnectorHookRoutesfrom./modules/social-connectors/ingress. Register both with{ prefix: '/v1' }beside existing social inbox registrations. Keep them separately encapsulated: only the hook registration installs the raw JSON parser. Do not replace existing social/inbox routes. - In the existing outreach MCP registration, import
registerSocialConnectorToolsfrom../social-connectors/mcp-tools, and invoke it with the same registration wrapper/context used byregisterSocialWeekTools. Add both names to any parent-owned static discovery/census declarations. Add the status GET and receipt readback POST paths to the canonical outreach key allowlist/dark-route set, and classify the lookup-only readback POST as read-only. Exact hunks are in the task PARENT-INTEGRATION.md. Preserve current credential ceilings and feature guards. - Mount
ConnectorPanelfromapps/web/src/components/social-connectors/connector-panel.tsxwithin existing project social settings. Supply orgId, projectId, and server-derived canEdit/canManage for project PM + org integration permission. Backend repeats every guard. The panel includes configuration, exact-copy review queue, provider readback and a scoped canonical inbox view. No new global navigation is supplied. - Configure an explicit Unipile v1 base URL and the qualified signing relay secret through canonical server configuration into app.connectorEndpoints.unipile and app.connectorRelaySecret. Provider endpoint overrides otherwise exist only for controlled local HTTP proof. Do not obtain credentials or register external webhooks until the authorized parent live-proof phase.
The owning suite mounts the isolated exported routes, registers MCP with the real SDK, applies DDL to current PG migrations, and serves a production React bundle in actual Chromium. This proves the module boundaries; it does not claim global app/MCP/nav integration or real vendor authorization. Screenshots and readbacks are under the task evidence directory. Independent Terra review is parent configured and must be recorded separately; author tests are not that review.