Surge

Ongoing Conversations

When the same person sends and receives multiple messages over time, Surge tracks them as a conversation thread. Understanding how conversations and contacts work helps you build two-way messaging flows, surface message history, and manage opt-out state correctly.

Contacts and conversations

A contact represents a phone number in the context of a specific account. When someone messages your number for the first time — or when you first message them — Surge creates a contact record and a conversation thread.

A conversation is the thread of all messages between your number and a contact. Every message you send or receive includes a conversation.id in the response.

{
  "id": "msg_01jrzhe8d9enptypyx360pcmxj",
  "blast_id": null,
  "body": "Your order is ready for pickup.",
  "attachments": [],
  "conversation": {
    "id": "cnv_01jav8xy7fe4nsay3c9deqxge9",
    "phone_number": {
      "id": "pn_01jsjwe4d9fx3tpymgtg958d9w",
      "number": "+18015556789",
      "type": "local"
    },
    "contact": {
      "id": "ctc_01ja88cboqffhswjx8zbak3ykk",
      "phone_number": "+18015551234"
    }
  },
  "metadata": {}
}

When to use contacts vs raw messaging

Raw messaging (passing a phone number directly in the to field) is fine for simple one-way notifications where you don't need to track who sent what or manage opt-out state per person. Blast sends work this way.

Contacts become useful when:

  • You're building two-way messaging and need to correlate inbound replies with specific customers
  • You need to check or manage opt-out state for an individual
  • You want to associate metadata (name, account ID in your system) with a phone number
  • You're building embeddable UI components that need to show a contact's message history

Create a contact explicitly to attach metadata before the first message:

curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/contacts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+18015551234",
    "first_name": "Jordan",
    "last_name": "Patel",
    "email": "jordan.patel@example.com",
    "metadata": {
      "customer_id": "cust_9887"
    }
  }'
{
  "id": "ctc_01jrzhe8d9enptypyx360pcmxn",
  "phone_number": "+18015551234",
  "first_name": "Jordan",
  "last_name": "Patel",
  "email": "jordan.patel@example.com",
  "metadata": {
    "customer_id": "cust_9887"
  }
}

Contacts support these fields: phone_number (required), first_name, last_name, email, date_of_birth, and metadata.

All contact PII is encrypted at rest. first_name, last_name, email, date_of_birth, phone_number, and metadata are stored encrypted in the database. You can safely store customer-identifying information here without it being readable if the database is compromised.

Surge also automatically populates carrier data (phone_carrier, phone_carrier_name, phone_country) on contacts based on carrier lookups. This data is populated asynchronously after the first message send or verification — it is not available at contact creation time.

Following a conversation

When a contact sends you an inbound message, the message.received webhook event includes the conversation ID. Use that ID to fetch the full thread or send a reply into the same conversation.

# Send a reply into an existing conversation
curl -X POST https://api.surge.app/accounts/YOUR_ACCOUNT_ID/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": "cnv_01jav8xy7fe4nsay3c9deqxge9",
    "body": "Your order is at the front desk — no signature needed."
  }'

When you send into a conversation, Surge routes the message from the same number the contact previously reached.

Conversation status

A conversation has one of three statuses:

StatusMeaning
activeThe conversation is open; messages can be sent and received
archivedThe conversation has been closed in the dashboard but the contact has not opted out. Sending to an archived conversation re-opens it automatically
opted_outThe contact replied with a recognised opt-out keyword. Sending returns an opted_out error until they re-opt-in

The opted_out_at timestamp is set when the conversation moves to opted_out status.

Opt-out state

Opt-out state lives on the conversation (one contact might opt out of one number but remain active on another). When a contact replies STOP, the conversation.opted_out_at timestamp is set and the contact.opted_out webhook event fires.

Sending to an opted-out conversation returns an opted_out error. Do not attempt to send around opt-outs — doing so violates carrier policies and can result in your campaign being deactivated.

A contact re-opts in by texting START, YES, or UNSTOP to the same number. When that happens, contact.opted_in fires and the conversation status returns to active.

Audiences

If you want to group contacts into named lists for repeated sends, use audiences. An audience is a collection of contacts that you blast together. See Send to Many People for the full pattern.

Cross-channel tracking (roadmap)

Conversations and contacts today are SMS-specific. As Surge adds RCS, WhatsApp, and other channels, the contact model will extend to track communication history across all channels in a single thread. See Channels Roadmap.