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 the webhook configured for the channel.

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 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 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 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. 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, 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.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 errors[] 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.
  • 409externalReference 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 and its configured webhook.
  • 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