Audiences

Reusable contact groups you can target with a single templated send.

Model

Table Purpose
audiences Named group (slug-unique per org)
contacts People with email / phone / name / free-form metadata
audience_members Join table

Contact metadata columns from CSV imports become {{variables}} available to template rendering at send time. Values in the variables field of the send request take precedence over contact metadata with the same key.

Public REST

Send to every member

POST /api/v1/audiences/:id/send
{
  "channel": "email",
  "from": "hello@yourdomain.com",
  "template": "weekly-newsletter",
  "variables": { "cta": "Read more" },
  "ramp_minutes": 60,
  "subject_variants": [
    "Your weekly update",
    "Quick update for {{name}}"
  ],
  "scheduled_local": "2026-05-01 09:00",
  "scheduled_at_tz": "America/New_York"
}

Returns:

{
  "audience_id": "aud_...",
  "total": 4200,
  "scheduled": 4180,
  "suppressed": 20,
  "starts_at": "2026-05-01T13:00:00.000Z",
  "ends_at": "2026-05-01T14:00:00.000Z"
}
  • total = audience members, scheduled = rows inserted into messages with status="scheduled", suppressed = filtered by the org suppression list.
  • ramp_minutes (0–1440) spreads sends evenly across the window; default fires all at once.
  • subject_variants (2–5) runs an A/B on the email subject. Each contact is assigned deterministically (hash of contact id mod N), so retries stay on the same variant. The chosen variant letter (A, B, …) lands in messages.metadata.subject_variant for downstream attribution.
  • Max recipients per call: 10_000. Split bigger audiences.

Scope: write:audiences.

Dashboard

/overview/audiences lists audiences with contact counts and surfaces an inline Import / Send drawer per audience (owners only).

CSV import

Internal-only — POST /api/internal/audiences/:id/import-csv accepts a CSV payload:

email,name,company
alice@example.com,Alice,Acme
bob@example.com,Bob,Widgetco
  • Required header column: email or phone (or both).
  • Optional: name.
  • Every other column becomes a key on contacts.metadata.
  • Max 10,000 rows per upload. Split larger files.

Content-type either text/csv (body = raw CSV) or application/json with { "csv": "..." }.

Audits as audience.csv_imported.