Audience & broadcasts

Contacts

Store the people you email, organize them with segments, topics and properties, and manage them in the dashboard or over the API.

A contact is one email address in a project, with an optional name, custom properties, segment memberships and topic preferences. You send marketing email to contacts through broadcasts.

Contacts belong to a project. Two projects in the same team can each hold a contact with the same email address.

Create a contact

In the dashboard, open Audience, choose Contacts and add an email address with an optional first and last name. With the API, use a full_access key:

Shell
curl -X POST https://api.sooner.dev/contacts \
  -H "Authorization: Bearer $SOONER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@example.com",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "properties": { "company": "Acme" },
    "segments": [{ "id": "jd5f2h8k4m7p1r6t9v3x0z2c" }],
    "topics": [{ "id": "jt3b6n9q2w5e8r1y4u7i0o2p", "subscription": "opt_in" }]
  }'
JSON
{ "object": "contact", "id": "jx7r4k9m2c5v8n1b3q6w0e2t" }

Only email is required. See the contacts API reference for every field.

  • The address is stored in lowercase. A project can hold one contact per address, and creating a duplicate returns 422 validation_error.
  • Every key in properties must be declared first. See Contact properties.
  • Every segment and topic ID must exist in the project, or the request returns 404 not_found.

Find, update and delete a contact

The {identifier} in contact paths is either the contact ID or the email address. Both work everywhere.

Shell
curl https://api.sooner.dev/contacts/ada@example.com \
  -H "Authorization: Bearer $SOONER_API_KEY"
JSON
{
  "object": "contact",
  "id": "jx7r4k9m2c5v8n1b3q6w0e2t",
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Lovelace",
  "unsubscribed": false,
  "properties": { "company": "Acme" },
  "created_at": "2026-11-14T10:30:00.000Z"
}

An update changes only the fields you send. Properties are merged into the existing ones, so you can update one property without resending the rest. Send null for first_name, last_name or a property to clear it.

Shell
curl -X PATCH https://api.sooner.dev/contacts/ada@example.com \
  -H "Authorization: Bearer $SOONER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "properties": { "company": "Analytical Engines" } }'

Deleting a contact removes its segment memberships and topic preferences and frees one slot in your contact limit.

Unsubscribed contacts

unsubscribed is the contact's global opt-out. A contact with unsubscribed: true is skipped by every broadcast. Contacts set it themselves through the unsubscribe link in your emails, and you can set it yourself with the API. See Unsubscribes.

Setting unsubscribed back to false makes the contact eligible again. Only do this when the person has asked to receive your email again.

Segments and topics

A contact can be in any number of segments, and can opt in to or out of any number of topics. Manage both from the contact, or from the segment and topic side:

TaskEndpoint
Add to a segmentPOST /contacts/{identifier}/segments/{segmentId}
Remove from a segmentDELETE /contacts/{identifier}/segments/{segmentId}
List a contact's segmentsGET /contacts/{identifier}/segments
Set topic preferencesPATCH /contacts/{identifier}/topics
List a contact's topicsGET /contacts/{identifier}/topics

Contact limits

The limit applies to the whole team, across all of its projects.

PlanContacts
Free2,000
Pro25,000
Scale150,000
EnterpriseUnlimited

When you reach the limit, creating a contact returns 422 validation_error with the message "Your plan allows 2,000 contacts. Upgrade to add more." Deleting contacts frees space immediately. Check your current count with GET /usage.

Webhooks

Creating, updating and deleting a contact fires contact.created, contact.updated and contact.deleted. See Event types.

Importing contacts

Sooner has no file import. To load an existing list, create contacts in a loop, staying under your plan's request rate limit:

TypeScript
const contacts = [
  { email: "ada@example.com", first_name: "Ada" },
  { email: "grace@example.com", first_name: "Grace" },
];

for (const contact of contacts) {
  const res = await fetch("https://api.sooner.dev/contacts", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SOONER_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ...contact, segments: [{ id: "jd5f2h8k4m7p1r6t9v3x0z2c" }] }),
  });
  if (!res.ok) console.error(contact.email, await res.json());
}