Contacts

Per-brand contact database. Tags instead of nested lists. Custom fields. Bulk operations.

The data model

  • Email — primary key. One contact per email per brand.
  • first_name, last_name — optional.
  • statussubscribed | 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/:apiKey from 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=subscribed

Bulk 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.