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 intomessageswithstatus="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 inmessages.metadata.subject_variantfor 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:
emailorphone(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.