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,date_of_birth,phone_number, andmetadataare 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:
| Status | Meaning |
|---|---|
active | The conversation is open; messages can be sent and received |
archived | The conversation has been closed in the dashboard but the contact has not opted out. Sending to an archived conversation re-opens it automatically |
opted_out | The 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.