Send an outbound message via the API
You’ll use this when you have a touchpoint outside Atender — a custom in-app chat, a marketplace inbox, a legacy tool — and you want the resulting conversation to land in Atender’s inbox.
Before you start
- A tenant API key with the
conversations:writescope. Generate one in Settings → API Keys if you don’t have one yet. - Decide on an externalReference scheme for your conversations. This is a string you supply that uniquely identifies the conversation in your system — it makes retries safe.
Request
POST https://YOUR-ATENDER-HOST/v1/conversations/outbound
Headers:
Authorization: Bearer sa_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Body:
{
"externalReference": "your-system-123",
"contact": {
"email": "customer@example.com",
"name": "Optional Name",
"phone": "+15551234567"
},
"subject": "Order #4521 — refund request",
"channel": "email",
"outboundContext": {
"originalMessage": "The customer's original message body",
"sentAt": "2026-05-11T09:00:00Z",
"metadata": { "source": "shopify" }
},
"tags": ["refund", "vip"],
"customFields": { "order_id": "4521" }
}
Required fields
externalReference— string (max 255) — Unique per tenant. Reusing the same reference returns 409 — useful for safely retrying network failures.contact.email— string — Valid email. Atender uses this to match (or create) a contact.subject— string — Conversation subject.
Optional fields
contact.name— string — Contact’s full name. If omitted, the email’s local part is used.contact.phone— string — Contact’s phone number.channel— enum — One ofemail(default),messenger,sms,phone,voice,whatsapp,webchat,amazon. Tag the conversation with the channel it really came from in your system.outboundContext.originalMessage— string — The first message body as plain text.outboundContext.originalHtml— string — The first message body as HTML, if you have it.outboundContext.sentAt— string — ISO timestamp of when the message was originally sent.outboundContext.metadata— object — Free-form metadata for your own use.tags— string[] — Tag slugs to attach to the conversation. Slugs that don’t exist will be ignored.customFields— object — Custom-field values keyed by field slug.
Response
On success, you get a 201 Created with the conversation:
{
"id": "...",
"externalReference": "your-system-123",
"status": "active",
...
}
Verify it worked
- Open the inbox in Atender. The new conversation appears in Active, assigned to the team that owns the channel (or the default team if no routing rules match).
- Reply to the conversation in Atender. If you’ve also subscribed to webhook events, your server will receive a
message.sentevent with the reply payload — that’s how you ship the agent’s reply back to wherever the customer is.
Errors to handle
400— Validation error — Check required fields. The response body haserrors[]pointing to the offending fields.401— Bad or missing API key — Re-check theAuthorizationheader.403— Wrong scope — Your API key needsconversations:write.409—externalReferencealready used — A conversation already exists for that reference. Use a different reference, or fetch the existing conversation by reference.5xx— Server error — Retry with exponential backoff. Because you supplied anexternalReference, a retry that succeeds after a 5xx will not create a duplicate.
Patterns
- Always supply an
externalReference. It is what makes retries safe. If your network blip causes you to receive a 5xx but the conversation was actually created, your retry will get a 409 and you can move on. - Use the
channelfield honestly. If the conversation came from Instagram, setchannel: "messenger". The channel badge in the inbox is set from this field, and downstream filters and analytics depend on it. - Store the returned
idalongside yourexternalReferenceso you can correlate later. You’ll need it to fetch the conversation, push replies, or correlate webhook events.
Limits
- The
externalReferencemust be unique per tenant. Conflicts return 409. - Validation enforces a max length of 255 on
externalReferenceand a valid email oncontact.email.