> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drin.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Contacts

> The per-project address book: create, list, update, and delete contacts, with subscribe and unsubscribe state you own.

A lightweight address book scoped to each project — store the people you email, attach your own metadata, and track their subscribe state. It's a record-keeping layer, not a marketing list.

Contacts let you keep names, custom `metadata`, and subscribe status alongside an email address. Each **project** has its own address book. Drin stays transactional — there are no broadcasts or campaigns here — but a contact record is a convenient home for the data your own send logic reads.

<Info>
  **Contacts vs suppressions.** A contact's `subscribed` flag is *your* opt-in state — you decide what it means and whether to honour it before sending. [Suppressions](/suppressions) are *enforced* by Drin at send time and populated automatically by bounces and complaints. They're separate systems; unsubscribing a contact does not suppress the address, and vice versa.
</Info>

## Create a contact

**`POST /v1/contacts`**

Only `email` is required. `firstName`, `lastName`, `subscribed` (defaults to `true`), and an arbitrary `metadata` object are optional.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.drin.run/v1/contacts \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "sam@example.com",
      "firstName": "Sam",
      "lastName": "Rivera",
      "metadata": { "plan": "pro" }
    }'
  ```

  ```typescript Node.js theme={null}
  const contact = await drin.contacts.create({
    email: "sam@example.com",
    firstName: "Sam",
    lastName: "Rivera",
    metadata: { plan: "pro" },
  });
  ```
</CodeGroup>

```json 201 Contact theme={null}
{
  "id": "ct_0K3a…",
  "email": "sam@example.com",
  "firstName": "Sam",
  "lastName": "Rivera",
  "subscribed": true,
  "unsubscribedAt": null,
  "metadata": { "plan": "pro" },
  "createdAt": "2026-06-02T09:00:00Z",
  "updatedAt": "2026-06-02T09:00:00Z"
}
```

## List & search

**`GET /v1/contacts`**

Cursor-paged like every list endpoint. Filter with `subscribed=true|false` and search names and email with `q`.

<CodeGroup>
  ```bash cURL theme={null}
  # All contacts
  curl "https://api.drin.run/v1/contacts?limit=50" \
    -H "Authorization: Bearer $DRIN_API_KEY"

  # Only subscribed, matching a search term
  curl "https://api.drin.run/v1/contacts?subscribed=true&q=rivera" \
    -H "Authorization: Bearer $DRIN_API_KEY"
  ```

  ```typescript Node.js theme={null}
  // Auto-page every subscribed contact.
  for await (const c of drin.contacts.paginate({ subscribed: true })) {
    console.log(c.email, c.firstName);
  }
  ```
</CodeGroup>

## Update

**`PATCH /v1/contacts/{id}`**

Partial update — send only the fields you want to change. `firstName` and `lastName` accept `null` to clear them. To change subscribe state, use the dedicated endpoints below rather than patching it directly.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.drin.run/v1/contacts/ct_0K3a \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "lastName": null, "metadata": { "plan": "scale" } }'
  ```

  ```typescript Node.js theme={null}
  await drin.contacts.update("ct_0K3a", {
    lastName: null,                 // clear a field by sending null
    metadata: { plan: "scale" },
  });
  ```
</CodeGroup>

## Unsubscribe & resubscribe

**`POST /v1/contacts/{id}/unsubscribe`**

Unsubscribing flips `subscribed` to `false` and stamps `unsubscribedAt`. Resubscribing flips it back and clears the timestamp. Both return the updated contact.

<CodeGroup>
  ```bash cURL theme={null}
  # Unsubscribe (stamps unsubscribedAt, flips subscribed → false)
  curl -X POST https://api.drin.run/v1/contacts/ct_0K3a/unsubscribe \
    -H "Authorization: Bearer $DRIN_API_KEY"

  # Resubscribe (clears unsubscribedAt, flips subscribed → true)
  curl -X POST https://api.drin.run/v1/contacts/ct_0K3a/resubscribe \
    -H "Authorization: Bearer $DRIN_API_KEY"
  ```

  ```typescript Node.js theme={null}
  await drin.contacts.unsubscribe("ct_0K3a");
  // …later…
  await drin.contacts.resubscribe("ct_0K3a");
  ```
</CodeGroup>

<Tip>
  **Honouring opt-out.** These endpoints record intent — they don't block sending on their own. Check `subscribed` in your own send path, or add the address to [suppressions](/suppressions) when you want Drin to enforce the opt-out at the gateway.
</Tip>

## Delete

**`DELETE /v1/contacts/{id}`**

Permanently removes the contact record. Returns `204 No Content`. This deletes the address-book entry only — it has no effect on already-sent messages or the suppression list.

```bash cURL theme={null}
curl -X DELETE https://api.drin.run/v1/contacts/ct_0K3a \
  -H "Authorization: Bearer $DRIN_API_KEY"
```

## Related

<CardGroup cols={2}>
  <Card title="Suppressions" icon="shield" href="/suppressions">
    The enforced do-not-send list — separate from contact opt-out.
  </Card>

  <Card title="Send email" icon="bolt" href="/sending">
    Read a contact, decide on `subscribed`, then send.
  </Card>

  <Card title="Contacts API" icon="code" href="/api-reference/contacts/create">
    Every parameter and response schema for the contacts endpoints.
  </Card>

  <Card title="TypeScript SDK" icon="code" href="/sdk/typescript">
    The `drin.contacts` resource and `.paginate()`.
  </Card>
</CardGroup>
