How to Build a Unified Social Media Inbox via API
One chat and message schema across Facebook Messenger, Instagram DMs, Telegram, and Bluesky — list conversations, receive webhooks, and reply from a single inbox API.
One schema, four platforms
The Direct Messages API normalizes Facebook Messenger, Instagram, Telegram, and Bluesky into two resources:
- Chat — a conversation with one participant:
participant_name,participant_avatar_url,last_message_at, platform-specificmetadata. - Message —
direction(inbound/outbound),body,attachments,status, timestamps.
Your inbox renders these two objects. The platform differences — Meta webhooks vs Telegram Bot API vs AT Protocol polling — stay on Postproxy’s side of the line.
The conversation list
Pull chats per connected profile, ordered by latest activity:
curl "https://api.postproxy.dev/api/profiles/PROFILE_ID/chats?per_page=20" \ -H "Authorization: Bearer YOUR_API_KEY"Merge the lists from all profiles, sort by last_message_at, and that’s the left pane. Each chat already has the participant’s display name and a stable avatar URL — no platform lookups to render a row. before / after timestamp filters handle incremental refresh.
The thread view
curl "https://api.postproxy.dev/api/chats/CHAT_ID/messages?per_page=50" \ -H "Authorization: Bearer YOUR_API_KEY"Messages come newest-first with everything a thread UI needs:
directiondecides bubble alignment.attachmentscarry stable storage URLs — inbound media is mirrored off platform CDNs, so images don’t break when Meta’s tokens expire.external_read_at/external_delivered_atgive read receipts (Facebook and Instagram).reactionsis the live reaction state per message.external_edited_atandexternal_deleted_atlet you render “edited” and tombstones.
Real-time updates
Subscribe one webhook endpoint to the message events and fan out to your UI over your own websocket/SSE:
message.received— new inbound message (the event that bumps a chat to the top)message.sent,message.delivered,message.read— outbound lifecyclemessage.edited,message.deleted— thread mutationsreaction.received— reactions, including removals
One caveat: Bluesky has no webhooks upstream, so inbound Bluesky messages arrive via a ~5-minute poller. The message.received event still fires — just with up to 5 minutes of latency.
Replying
Same send call regardless of platform:
curl -X POST "https://api.postproxy.dev/api/chats/CHAT_ID/messages" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "body": "On it — refund issued, you should see it in 3-5 days." }'Track the returned message’s status (pending → published, or failed with error_details) to show send state in the composer, or let message.sent / message.failed webhooks drive it.
Capability differences to surface in the UI
The schema is uniform; platform abilities aren’t. Disable composer features per chat’s platform:
| Capability | Telegram | Bluesky | ||
|---|---|---|---|---|
| Attachments | Yes (1/send) | Yes (1/send) | Yes | No |
| Reactions | Yes | Yes | No | No |
| Edit sent message | No | No | Yes | No |
| Read receipts | Yes | Yes | No | No |
| Free-form send window | 24h after last inbound | 24h after last inbound | Any time after first user DM | Any time |
The 24-hour rule is the one to design around: when a Meta chat’s last_inbound_at is older than 24 hours, grey out the composer — the send would fail anyway. (Private replies are the comment-triggered exception.)
Beyond DMs
The same inbox pattern extends to public interactions: comments on Instagram, Facebook, and Threads, and Google Business reviews — all webhook-driven, all replied to with one POST. Per-platform send guides: Instagram DMs, Facebook Messenger.