Surge

Send to One Person

All outbound messages — SMS and MMS — go through the same endpoint:

POST /accounts/:account_id/messages

Send an SMS

The minimum required fields are to (the recipient's phone number in E.164 format) and body (the message text). If you've configured a default phone number on the account, you don't need to specify from.

curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18015551234",
    "body": "Your appointment is confirmed for Friday at 2pm."
  }'

To send from a specific number, include its ID or E.164 number as from:

curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18015551234",
    "from": "pn_01jrzhe8d9enptypyx360pcmxm",
    "body": "Your appointment is confirmed for Friday at 2pm."
  }'

The response includes the message ID, body, and the conversation thread the message belongs to:

{
  "id": "msg_01jrzhe8d9enptypyx360pcmxj",
  "blast_id": null,
  "body": "Your appointment is confirmed for Friday at 2pm.",
  "attachments": [],
  "conversation": {
    "id": "cnv_01jrzhe8d9enptypyx360pcmxk"
  },
  "metadata": {}
}

Tracking delivery

Message delivery status isn't a field on the REST response — it comes through webhook events. Subscribe to these two event types:

EventMeaning
message.deliveredCarrier confirmed the message reached the handset (or was handed off to the carrier for local numbers)
message.failedThe message could not be delivered; the reason field in the event explains why

Not all carriers send delivery receipts, so message.delivered isn't guaranteed for every carrier. See Webhook Events for the full payload shapes.

Send an MMS

Include an attachments array to send MMS. Each item needs a url pointing to a publicly accessible file.

curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18015551234",
    "body": "Here is your invoice.",
    "attachments": [
      { "url": "https://acme.com/invoices/inv-1234.pdf" }
    ]
  }'

The response includes the attachment IDs. To retrieve an attachment file later, use GET /attachments/:id/file.

MMS gotchas

  • Content type — some carriers reject attachment types they don't support. Common safe types are image/jpeg, image/png, and image/gif. PDFs deliver reliably to most handsets but not all.
  • File size — very large files may be silently truncated or fail delivery on certain networks. Keep attachments under 1 MB where possible.
  • Body is optional — you can send an MMS with attachments only, no body text.
  • Attachment URL restrictions — Surge fetches the attachment URL to build the MMS. The URL must be publicly accessible over https:// or http:// (no other schemes). URLs that resolve to private IP addresses (10.x.x.x, 172.16.x.x, 192.168.x.x, 127.x.x.x, etc.) or use reserved TLDs (.local, .internal, .localhost, .test) are blocked. This means you cannot use localhost or internal network URLs for attachments, even in development. Host test files on a public URL or use a tunneling service like ngrok.

Automatic SMS-to-MMS conversion

Surge can automatically upgrade a plain SMS to MMS in two situations:

  1. Message exceeds 10 segments — any message over 10 segments (around 1,530 GSM-7 characters) is automatically sent as MMS regardless of whether you included attachments.
  2. Message is 3 or more segments and auto_mms_enabled is on for your platform — contact support to enable this platform-level setting if you want long messages to convert automatically.

When a message is upgraded to MMS, it sends as a single media message rather than a concatenated SMS sequence. If the recipient's carrier doesn't support MMS (mms_not_supported), Surge falls back to SMS.

You never need to detect this yourself — the message.sent and message.delivered events reflect the actual protocol used.

Schedule a message

Set send_at to an ISO 8601 timestamp to schedule a message for later delivery. The maximum scheduling window is 65 days from now.

curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18015551234",
    "body": "Your trial ends tomorrow — upgrade to keep your data.",
    "send_at": "2026-06-01T09:00:00Z"
  }'

A scheduled message is accepted and queued immediately. Delivery events (message.delivered, message.failed) fire when the message is actually sent at the scheduled time.

Personalise messages with contact variables

If the recipient has a contact record, Surge substitutes these tokens in the message body at send time:

TokenReplaced with
{first_name}Contact's first name, or empty string if not set
{last_name}Contact's last name, or empty string if not set
{full_name}First and last name joined, or empty string if neither is set
curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18015551234",
    "body": "Hi {first_name}, your order has shipped!"
  }'

If the contact's first_name is Jordan, the recipient receives Hi Jordan, your order has shipped!. If the contact has no first_name, the {first_name} token is replaced with an empty string — the recipient gets Hi , your order has shipped!. Set names on your contacts before sending personalised messages to avoid awkward blanks. Tokens are replaced when you pass to (a phone number or contact ID); they are not replaced on blast sends that bypass contact resolution.

Add metadata

metadata is a free-form JSON object you can attach to any message. It's returned in webhook events and list responses, which makes it useful for correlating Surge messages with records in your own system.

{
  "to": "+18015551234",
  "body": "Your order has shipped.",
  "metadata": {
    "order_id": "ord_9887",
    "customer_tier": "premium"
  }
}

Metadata is encrypted at rest. Message metadata is stored encrypted in Surge's database. Store any data here you'd normally want protected — internal IDs, tier markers, contextual details about the send.

Keep metadata values below a few KB. Very large metadata objects have no hard size limit enforced at the API layer, but they are embedded in every webhook payload that references the message, which can make your webhook handler payload processing heavier than necessary.

Common errors

Error typeCauseFix
opted_outThe recipient has replied STOPDo not send to opted-out contacts
demo_message_limitYou've hit the 25-message demo capRegister and buy a number
link_size_limitThe message body contains too many URLsReduce links or use link shortening
invalid_content_typeAttachment type unsupported by the carrierUse a supported image format

Full error reference is in Handle Failures and the Error Reference.