Engagement

Inbox

Read and send direct messages on Instagram, Facebook and Bluesky, including quick replies, buttons and cards.

Read and answer an account's direct messages, start conversations, and send buttons, quick replies and cards. Messages are read live from the platform.

Support by Platform

FeatureInstagramFacebookBluesky
List and read
Start a conversation withUsername or user idPage-scoped idHandle or DID
Message length1,000 characters2,000 characters1,000 graphemes
Sending window24 hours24 hoursNone
Rich messagesNo
Received attachmentsNo
Ice breakers and menuNoNo
Inbox webhooksNo
Private replies, automationsNo

Other platforms return 400 UNSUPPORTED_PLATFORM.

The account needs messaging access, approved at connect. Without it you get 422 INBOX_PERMISSION, so reconnect and approve every permission. On Bluesky, the App Password needs direct message access.

List Conversations

GET /v1/accounts/{id}/conversations lists conversations, newest first. Each page holds 50 by default, or up to 100 with limit. Pass nextCursor as cursor until hasMore is false to read them all.

import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.listConversations({
  id: "acc_YOUR_INSTAGRAM_ACCOUNT_ID",
});
console.log(response);
{
  "data": [
    {
      "id": "cnv_u1rTltEvz7pEbzpQOLjQAlMVsqWCdakS9U2wjAFB6CV6SYgY3Bx8GTrVhAn9njOg3eB2YWHX",
      "accountId": "acc_YOUR_INSTAGRAM_ACCOUNT_ID",
      "members": [
        {
          "id": "17841401234567890",
          "username": "yourbrand",
          "displayName": "yourbrand"
        },
        {
          "id": "6873452198765432",
          "username": "jane_doe",
          "displayName": "jane_doe"
        }
      ],
      "muted": false,
      "unreadCount": 0,
      "lastMessage": {
        "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM2NDEyOTg3NjU0MzIxMDk4NzY1NDMy",
        "text": "Do you ship to Canada?",
        "sentAt": "2027-03-15T11:42:08.000Z"
      }
    },
    {
      "id": "cnv_UYeVH71CtvWwPasNAhEA9WXmVPAMJhQLmnGMapF5MxmPu59aNUBxoHt0T0YnAq98AujCL19c",
      "accountId": "acc_YOUR_INSTAGRAM_ACCOUNT_ID",
      "members": [
        {
          "id": "17841401234567890",
          "username": "yourbrand",
          "displayName": "yourbrand"
        },
        {
          "id": "7120984456321987",
          "username": "mark.builds",
          "displayName": "mark.builds"
        }
      ],
      "muted": false,
      "unreadCount": 0,
      "lastMessage": {
        "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM1MzQ1Njc4OTAxMjM0NTY3ODkwMTIz",
        "text": "Thanks, it arrived today!",
        "sentAt": "2027-03-15T10:17:45.000Z"
      }
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
FieldContains
idThe cnv_ id
accountIdThe conversation's account
membersEach person's platform id, username and displayName, the last two null when hidden
mutedtrue when muted. Bluesky only
unreadCountUnread messages, or 0 when not reported
lastMessageThe newest message's id, text and sentAt, or null

GET /v1/accounts/{id}/conversations/{conversationId} returns a single conversation, with the same fields as the list.

Conversation Ids

A cnv_ id works only in the workspace that received it. On Instagram and Facebook, one conversation can have two ids. List Conversations returns one, and Start a Conversation and webhooks return the other. Both work.

Read Messages

GET /v1/accounts/{id}/conversations/{conversationId}/messages lists messages, newest first. Paging works the same as for conversations.

import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.listMessages({
  id: "acc_YOUR_INSTAGRAM_ACCOUNT_ID",
  conversationId: "cnv_YOUR_CONVERSATION_ID",
});
console.log(response);
{
  "data": [
    {
      "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM2NDEyOTg3NjU0MzIxMDk4NzY1NDMy",
      "conversationId": "cnv_YOUR_CONVERSATION_ID",
      "text": "Do you ship to Canada?",
      "senderId": "6873452198765432",
      "sentAt": "2027-03-15T11:42:08.000Z",
      "attachments": []
    },
    {
      "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM2NDEyOTg3NjU0MzIxMDk4NzY1NDE5",
      "conversationId": "cnv_YOUR_CONVERSATION_ID",
      "text": "Yes, it is in stock in every size.",
      "senderId": "17841401234567890",
      "sentAt": "2027-03-15T11:40:02.000Z",
      "attachments": []
    },
    {
      "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM2NDEyOTg3NjU0MzIxMDk4NzY1NDA3",
      "conversationId": "cnv_YOUR_CONVERSATION_ID",
      "text": "Hi! Is this jacket still available?",
      "senderId": "6873452198765432",
      "sentAt": "2027-03-15T11:38:40.000Z",
      "attachments": []
    },
    {
      "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDAxMjM0NTY3ODk6MzQwMjgyMzY2ODQxNzEwMzAwOTQ5MTI4MTM2NDEyOTg3NjU0MzIxMDk4NzY1Mzk1",
      "conversationId": "cnv_YOUR_CONVERSATION_ID",
      "text": null,
      "senderId": "6873452198765432",
      "sentAt": "2027-03-15T11:38:15.000Z",
      "attachments": [
        {
          "type": "image",
          "url": "https://lookaside.fbsbx.com/ig_messaging_cdn/?asset_id=18043219876543210&signature=AbxQ3kLm9pRtYv2WcZ8sN1"
        }
      ]
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
FieldContains
idThe message's platform id
conversationIdThe message's conversation
textThe text, or null for attachments only or a deleted Bluesky message
senderIdAn Instagram-scoped id, a Page-scoped id or a Bluesky DID
sentAtWhen it was sent, or null
attachmentsEach with a type and a direct url. Empty on Bluesky

An attachment type is image, video, audio, file or other.

Start a Conversation

POST /v1/accounts/{id}/conversations with a recipient starts a one-to-one conversation with that person and returns 201.

import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.createConversation({
  id: "acc_YOUR_BLUESKY_ACCOUNT_ID",
  createConversationBody: {
    "recipient": "alice.bsky.social"
  },
});
console.log(response);
{
  "id": "cnv_xZWwUFsb6q2nrlD7GUiqQxNHfiqbgdNAjqlcvDLTpafW1UHpqYkJAIRnqf5uJ71zS3gigSZp",
  "accountId": "acc_YOUR_BLUESKY_ACCOUNT_ID",
  "members": [
    {
      "id": "did:plc:4y7hqd2kz3rbe6mfxw5tnc2a",
      "username": "yourbrand.bsky.social",
      "displayName": "Your Brand"
    },
    {
      "id": "did:plc:z72i7hdynmk6r22z27h6tvur",
      "username": "alice.bsky.social",
      "displayName": "Alice"
    }
  ],
  "muted": false,
  "unreadCount": 0,
  "lastMessage": null
}
PlatformrecipientNotes
InstagramA username, @ optional, or a numeric user idUsernames work for public Business and Creator accounts. Otherwise use a senderId
FacebookA numeric Page-scoped idFind it in members or a senderId
BlueskyA handle, @ optional, or a DIDAn existing conversation comes back as is

On Instagram and Facebook, a new conversation is readable once it has a message. Until then you get 404 CONVO_NOT_FOUND and no messages.

The person must have written in the last 24 hours, or sending fails with 409 MESSAGE_WINDOW_EXPIRED. On Bluesky, the recipient's settings decide, and follow-only settings or a block return 409 RECIPIENT_UNREACHABLE.

Send a Message

POST /v1/accounts/{id}/conversations/{conversationId}/messages sends a message and returns 201. text is required and must fit the message length.

import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.sendMessage({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  conversationId: "cnv_YOUR_CONVERSATION_ID",
  sendMessageBody: {
    "text": "Yes, we deliver on Sundays from 10:00 to 14:00."
  },
});
console.log(response);
{
  "id": "m_Xq2b7KfRz1nT8vLp3sWcYd9GhJ0aMeU4oQiN5kB6xZtVrHlF2gDsAy7wEcP1uOjI3mK8nL",
  "conversationId": "cnv_YOUR_CONVERSATION_ID",
  "text": "Yes, we deliver on Sundays from 10:00 to 14:00.",
  "senderId": "104987654321098",
  "sentAt": "2027-03-15T12:05:31.000Z",
  "attachments": []
}

Instagram and Facebook accept messages only within 24 hours of the person's last one, and message tags are not available. After the window closes, you get 409 MESSAGE_WINDOW_EXPIRED until they write again.

Messages count toward action limits. Over one, you get 429 QUOTA_EXCEEDED, and error says when to retry. A platform slowdown returns 429 RATE_LIMITED.

Rich Messages

On Instagram and Facebook, these fields add something to open or tap. Bluesky returns 400 ATTACHMENT_UNSUPPORTED or 400 TEMPLATE_UNSUPPORTED.

FieldAddsLimits
attachmentUrlAn image, video, audio or PDFPublic HTTPS, up to 2,048 characters
quickRepliesTappable repliesUp to 13
buttonsButtons under the textUp to 3
cardsA swipeable carouselUp to 10, each with up to 3 buttons
ItemFields
Quick replytitle up to 20 characters, payload up to 1,000, optional imageUrl
Buttontype, title up to 20 characters, and url for web_url or payload for postback
Cardtitle and optional subtitle up to 80 characters, optional imageUrl and buttons
import { Configuration, InboxApi } from "@onepostly/sdk";

const inboxApi = new InboxApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await inboxApi.sendMessage({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  conversationId: "cnv_YOUR_CONVERSATION_ID",
  sendMessageBody: {
    "text": "Which pickup time works for you?",
    "quickReplies": [
      {
        "title": "Morning",
        "payload": "PICKUP_AM"
      },
      {
        "title": "Afternoon",
        "payload": "PICKUP_PM"
      }
    ]
  },
});
console.log(response);
{
  "id": "m_Rk5nW8cTq2vYb7LzP4sXdA1fGh9JmE3oUiK6tN0aBwVyQ2rHlC8gDsMx5eFpZ1uOjI7kL4",
  "conversationId": "cnv_YOUR_CONVERSATION_ID",
  "text": "Which pickup time works for you?",
  "senderId": "104987654321098",
  "sentAt": "2027-03-15T12:06:10.000Z",
  "attachments": []
}

A message has one main part, and text is always required.

You sendThe person sees
attachmentUrlThe attachment only
cardsThe carousel only, without outside buttons
buttonsThe text, cut at 640 characters, with buttons
Only textThe text

quickReplies go with any of these, but attachmentUrl together with buttons or cards in one message returns 400 INVALID_TEMPLATE.

The attachmentUrl extension sets how it is sent.

ExtensionSent as
.mp4, .mov, .m4v, .webm, .aviVideo
.mp3, .m4a, .aac, .wavAudio
.pdfFile
Anything elseImage
TappedYou receive
A quick replymessage.received with the title as text. Match on it, since payload is not sent
A postback buttonmessage.postback with its payload
A web_url buttonNothing. The link opens

Messenger Profile

A Facebook Page can greet people with up to 4 ice breaker questions in a new chat, and keep a menu of up to 3 items in every chat.

GET /v1/accounts/{id}/messenger-profile returns the setup.

import { Configuration, AccountsApi } from "@onepostly/sdk";

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.getMessengerProfile({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
});
console.log(response);
{
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "iceBreakers": [
    {
      "question": "What are your opening hours?",
      "payload": "HOURS"
    },
    {
      "question": "Do you deliver?",
      "payload": "DELIVERY"
    }
  ],
  "persistentMenu": [
    {
      "type": "web_url",
      "title": "Order online",
      "url": "https://example.com/order",
      "payload": null
    }
  ]
}

PUT on the same path changes it. Send iceBreakers, persistentMenu or both. Fields you leave out stay. The response is the setup after the change.

FieldEach itemLimits
iceBreakersquestion and payloadUp to 4. question up to 80 characters, payload up to 1,000
persistentMenutype, title, and url or payloadUp to 3. title up to 20 characters

A web_url item needs url, and a postback item needs payload.

import { Configuration, AccountsApi } from "@onepostly/sdk";

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.setMessengerProfile({
  id: "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  setMessengerProfileBody: {
    "iceBreakers": [
      {
        "question": "What are your opening hours?",
        "payload": "HOURS"
      },
      {
        "question": "Do you deliver?",
        "payload": "DELIVERY"
      }
    ],
    "persistentMenu": [
      {
        "type": "web_url",
        "title": "Order online",
        "url": "https://example.com/order"
      }
    ]
  },
});
console.log(response);
{
  "accountId": "acc_YOUR_FACEBOOK_ACCOUNT_ID",
  "iceBreakers": [
    {
      "question": "What are your opening hours?",
      "payload": "HOURS"
    },
    {
      "question": "Do you deliver?",
      "payload": "DELIVERY"
    }
  ],
  "persistentMenu": [
    {
      "type": "web_url",
      "title": "Order online",
      "url": "https://example.com/order",
      "payload": null
    }
  ]
}

Tapping an ice breaker or postback item sends message.postback with its payload. Other account types return 400 UNSUPPORTED_PLATFORM.

Webhooks

Instagram and Facebook inbox events keep your copy current without polling.

EventFires when
message.receivedSomeone messages the account
message.sentThe account sends, here or on the platform
message.delivered, message.readYour messages arrive, or are read
message.reactionThe person adds or removes a reaction
message.postbackThe person taps a button, menu item or ice breaker
message.deletedThe person unsends a message, Instagram only

Each event's conversationId works with the routes here. See Inbox Webhooks. Bluesky sends no events, so list its conversations instead.

Errors

CodeHTTPCauseFix
UNSUPPORTED_PLATFORM400The platform has no inboxSee Support by Platform
TEXT_REQUIRED, TEXT_TOO_LONG400text is empty or too longFit the message length
INVALID_TEMPLATE, INVALID_ATTACHMENT400A bad button, a mixed attachment, or a non-HTTPS URLFix the fields in error
CONVO_NOT_FOUND, RECIPIENT_NOT_FOUND404No such conversation or recipientCheck the id
MESSAGE_WINDOW_EXPIRED409The 24-hour window closedWait for the person to write
RECIPIENT_UNREACHABLE, CONVO_LOCKED409The person refuses messages, or the Bluesky chat is lockedReach them another way
TOKEN_EXPIRED, TOKEN_INVALID, CONNECTION_INACTIVE, CONNECTION_BROKEN409The account needs a reconnectReconnect it
INBOX_PERMISSION422No messaging accessReconnect and approve every permission
INBOX_FAILED502The platform did not complete itRetry. Reconnect if it keeps failing

Other codes are in Error Codes.