# Ongoing Conversations
URL: /docs/sending/conversations
LLM index: /llms.txt
Description: How contacts and conversation threads work, when to use them, and how to manage opt-out state per thread.

# 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.

```json
{
  "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:

<CodeGroup items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
from surge import Surge

surge = Surge()

contact = surge.contacts.create(
    account_id="{account_id}",
    phone_number="+18015551234",
    first_name="Jordan",
    last_name="Patel",
    email="jordan.patel@example.com",
    metadata={"customer_id": "cust_9887"},
)
```

```typescript TypeScript
import Surge from "@surgeapi/node";

const surge = new Surge();

const contact = await surge.contacts.create("{account_id}", {
  phone_number: "+18015551234",
  first_name: "Jordan",
  last_name: "Patel",
  email: "jordan.patel@example.com",
  metadata: { customer_id: "cust_9887" },
});
```

```ruby Ruby
require "surge_api"

surge = SurgeAPI::Client.new

contact = surge.contacts.create(
  "{account_id}",
  phone_number: "+18015551234",
  first_name: "Jordan",
  last_name: "Patel",
  email: "jordan.patel@example.com",
  metadata: { "customer_id" => "cust_9887" }
)
```

```elixir Elixir
client = Surge.Client.new(System.get_env("SURGE_API_KEY"))

{:ok, contact} =
  Surge.Contacts.create(client, "{account_id}", %{
    phone_number: "+18015551234",
    first_name: "Jordan",
    last_name: "Patel",
    email: "jordan.patel@example.com",
    metadata: %{customer_id: "cust_9887"}
  })
```

```bash cURL
curl -X POST https://api.surge.app/accounts/{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"
    }
  }'
```

</CodeGroup>

```json
{
  "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`.

<Note>
**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.
</Note>

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`](../receiving/events#message-received) webhook event includes the conversation ID. Use that ID to fetch the full thread or send a reply into the same conversation.

<CodeGroup items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
# Send a reply into an existing conversation
message = surge.messages.create(
    account_id="{account_id}",
    conversation={"id": "cnv_01jav8xy7fe4nsay3c9deqxge9"},
    body="Your order is at the front desk, no signature needed.",
)
```

```typescript TypeScript
const message = await surge.messages.create("{account_id}", {
  conversation: { id: "cnv_01jav8xy7fe4nsay3c9deqxge9" },
  body: "Your order is at the front desk, no signature needed.",
});
```

```ruby Ruby
message = surge.messages.create(
  "{account_id}",
  conversation: { id: "cnv_01jav8xy7fe4nsay3c9deqxge9" },
  body: "Your order is at the front desk, no signature needed."
)
```

```elixir Elixir
{:ok, message} =
  Surge.Messages.create(client, "{account_id}", %{
    conversation: %{id: "cnv_01jav8xy7fe4nsay3c9deqxge9"},
    body: "Your order is at the front desk, no signature needed."
  })
```

```bash cURL
curl -X POST https://api.surge.app/accounts/{account_id}/messages \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": {"id": "cnv_01jav8xy7fe4nsay3c9deqxge9"},
    "body": "Your order is at the front desk, no signature needed."
  }'
```

</CodeGroup>

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.

<Warning>
Do not attempt to send around opt-outs. Doing so violates carrier policies and can result in your campaign being deactivated.
</Warning>

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](./send-many) for the full pattern.

## Cross-channel tracking (roadmap)

<Info>
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](../channels-roadmap/rcs-and-whatsapp).
</Info>
