The data model
- Email — primary key. One contact per email per brand.
- first_name, last_name — optional.
- status —
subscribed|unsubscribed|bounced|complained. - tags — flat string list. Used for segmentation. See Tags.
- custom_fields — JSON object for anything else (workspace, plan, signup source, etc.).
- created_at, updated_at, last_event_at.
Adding contacts
- CSV import: Contacts → Import. See CSV import.
- Manual: Contacts → Add contact.
- Forms: a hosted/embedded form. See Forms.
- API:
POST /api/public/contacts/:apiKeyfrom your backend. - Integration sync: Firebase, Supabase, Google Sheets, Airtable.
Filtering
The contacts page accepts URL query params for shareable filters:
/brands/:brandId/contacts?tags=customer,paid&status=subscribedBulk operations
- Tag: select rows → "Add tag" / "Remove tag".
- Delete: hard-delete with confirmation. Cascades to events, send history, list memberships.
- Export: CSV download of the current filter.
Per-brand isolation
Contact
[email protected] in Brand A is a separate row from the same email in Brand B. Each brand has its own subscription status, tags, and history.