Channelsadvanced

Send an outbound message via the API

Create a conversation programmatically with POST /api/v1/conversations/outbound. Pass an externalReference for idempotent retries, a contact, a subject, and optionally the channel, tags, and custom fields.

8 min read

Send an outbound message via the API

You’ll use this when you want to create an outbound or programmatic conversation in Atender from another system.

This endpoint is not the Custom Channel ingress flow. For custom in-app chat, marketplace inboxes, or two-way external channels where customer messages enter Atender and agent replies are delivered back to your system, use a custom-channel integration instead. That flow uses POST /api/v1/channels, POST /api/v1/channels/{id}/messages, Idempotency-Key, attachment support, and reply delivery through either webhook push for channels configured with a webhook URL or pull delivery via GET /api/v1/conversations/{id}/messages for channels created without one.

Before you start

  • A tenant API key with the conversations:write scope. Generate one in Settings → API & MCP 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/api/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 message body you already sent to the customer",
    "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 of email (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 body of the message you already sent, as plain text.
  • outboundContext.originalHtml — string — The body of the message you already sent, 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 key.

Response

On success, you get a 201 Created. Every v1 response wraps its payload in a data object — read body.data, not body:

{
  "data": {
    "id": "...",
    "externalReference": "your-system-123",
    "replyToAddress": "reply+abc123@reply.your-atender-host",
    "status": "waiting",
    "createdAt": "2026-05-11T09:00:00.000Z"
  }
}

status here is the outbound status of the conversation — waiting until the contact replies. The conversation itself is created Active, which is where you will find it in the inbox.

Verify it worked

  • Open the inbox in Atender. The new conversation appears in Active, unassigned — this endpoint sets no team and no assignee.
  • Reply to the conversation in Atender. If you’ve also subscribed to webhook events, your server will receive a message.sent event 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 has error.details[] pointing to the offending fields.
  • 401 — Bad or missing API key — Re-check the Authorization header.
  • 403 — Wrong scope — Your API key needs conversations:write.
  • 409 — externalReference already 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 an externalReference, a retry that succeeds after a 5xx will not create a duplicate.

Patterns

  • Use this endpoint for outbound or programmatic conversations. If you are building a custom in-app chat, marketplace inbox, or another two-way external channel, use the custom-channel flow instead so inbound messages, attachments, idempotent retries, and Atender replies are handled through the channel. Replies are delivered through webhook push when the channel has a webhook URL, or by pull delivery via GET /api/v1/conversations/{id}/messages when the channel was created without one.
  • 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 channel field honestly. If the conversation came from Instagram, set channel: "messenger". The channel badge in the inbox is set from this field, and downstream filters and analytics depend on it.
  • Store the returned id alongside your externalReference so you can correlate later. You’ll need it to fetch the conversation, push replies, or correlate webhook events.

Limits

  • The externalReference must be unique per tenant. Conflicts return 409.
  • Validation enforces a max length of 255 on externalReference and a valid email on contact.email.

See also

Tags

How To