BitelioBitelio

Contacts

Store, enrich, and keep track of the people you email

A contact is Bitelio's record of a single email recipient: a unique identifier tied to an email address.

Adding contacts

There are several routes for getting contacts into a Bitelio project:

  • Via /v1/track — if you track an event against an email that isn't in the project yet, the contact is created on the fly (and starts out subscribed).
  • Via POST /contacts — creates or upserts one contact. If the email already exists it's updated rather than rejected; the response carries a _meta: { isNew, isUpdate } block, with status 201 when the contact was created and 200 when it was updated.
  • Bulk CSV import through POST /contacts/import — send a CSV (≤ 5 MB), then track the job with GET /contacts/import/:jobId. email must be the first column; every remaining column becomes a key under data.*.
  • Bulk subscribe / unsubscribe / delete through POST /contacts/bulk-subscribe, bulk-unsubscribe, and bulk-delete — each takes at most 1,000 contact IDs and hands back a job ID you can poll at GET /contacts/bulk/:jobId.
  • Pushing emails into a static segment using POST /segments/:id/members with createMissing: true, which creates any addresses not yet in the project.
  • By hand, from the dashboard.

Contact Data

Arbitrary key-value pairs can be attached to any contact, and that data then feeds segmentation and template personalization.

Data types

For each key in a project, Bitelio infers the type from the first non-null value it encounters:

TypeDescription
StringText that doesn't parse as a date.
NumberIntegers and floats alike.
Booleantrue or false.
DateStrings shaped like YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS[.sss]Z. Segment operators that understand dates require the full ISO 8601 form.

Type inference happens once per project, at first write. So if a key gets typed as number, later string values are still saved — but the segment filter UI keeps treating the key as a number. Mixed up the types by accident? The cleanest fix is DELETE /contacts/fields/:field to drop the field entirely, then re-import it with consistent values.

Default data type

Anything that isn't recognised as a number, a boolean, or an ISO 8601 date falls back to being a string.

Non-persistent values

Wrapping a value as { value, persistent: false } makes it available for a single render without ever writing it to the contact:

{
  "email": "user@example.com",
  "data": {
    "firstName": "Ada",
    "resetCode": { "value": "ABC123", "persistent": false }
  }
}

Here firstName gets stored on the contact, while resetCode exists only for this one send and is thrown away afterwards. That makes it the right tool for ephemeral secrets — password reset codes, magic links, one-time tokens.

Special value handling

A few values receive special treatment when a contact is created or updated:

ValueBehaviorExample
Empty string ("")Skipped — nothing is stored or changed{ name: "" } → Field is skipped
nullDeletes the field from the contact's data{ name: null } → Field is deleted
Other valuesWritten normally{ name: "John" } → Stored as "John"

Removing contact data

Pass null for a field when creating or updating a contact to erase it. An empty string won't do the same thing — empty strings are filtered out before the write, so existing data survives them.

Reserved keys

A handful of keys belong to Bitelio and live on the contact itself rather than in data. They're readable, but can't be written through data:

KeyDescription
emailThe contact's email address
createdAtWhen the contact was created
updatedAtWhen the contact was last modified
subscribedWhether the contact currently receives marketing email (boolean)

On top of that, these keys are silently stripped from any data payload — including them in /v1/track, POST /contacts, or PATCH /contacts/:id neither stores them nor produces an error:

id, bitelio_id, bitelio_email, unsubscribeUrl, subscribeUrl, manageUrl

The final three are generated fresh for each recipient at send time and surface as template variables (see Templates).

Special keys

KeyDescription
localeThe contact's preferred language as an ISO 639 code (e.g. 'en', 'fr', 'es'). When set, it takes precedence over the project-wide locale on contact-facing pages and in email footers

Subscription State

Each contact carries a subscribed flag governing which categories of email reach them. Contacts created through /v1/track start subscribed; with POST /contacts the flag takes whatever you send (defaulting to false).

On updates, leaving subscribed out preserves whatever it currently is — omission and false are different things. Send an explicit true or false whenever you intend a change.

Whenever subscribed changes, Bitelio records an event on the contact automatically:

  • flip to true → a contact.subscribed event
  • flip to false → a contact.unsubscribed event

This happens no matter what caused the flip — a dashboard edit, the public unsubscribe page, a bulk job, or an automatic unsubscribe following a bounce or spam complaint. Workflows can branch on both events.

How contacts become unsubscribed

There are several paths to the unsubscribed state:

  • Manual — someone toggles it in the dashboard or through the API.
  • Self-service — the contact clicks an unsubscribe link in an email, landing on the hosted public unsubscribe page.
  • Automatic on hard bounce — a permanent bounce unsubscribes the contact; soft bounces leave the state untouched.
  • Automatic on complaint — the contact reports your email as spam and their mailbox provider relays the complaint to Bitelio.

Emails by subscription state

Whether marketing email is delivered depends on the subscription flag; transactional email always goes through, subscribed or not.

Email typeSubscribedUnsubscribed
Transactional (via /v1/send)DeliveredDelivered
Campaigns (marketing)DeliveredNot delivered
Campaigns (headless)DeliveredNot delivered
Campaigns (transactional)DeliveredDelivered
Automations (marketing template)DeliveredNot delivered
Automations (headless template)DeliveredNot delivered
Automations (transactional template)DeliveredDelivered

Transactional emails and marketing templates

Going through the transactional endpoint (/v1/send) doesn't let a marketing template reach an unsubscribed contact — the subscription check still applies. If the message has to arrive regardless of opt-out, switch to a transactional template.

What's next