Contacts
A contact is a stored recipient: an email address plus whatever you know about the person behind it. Contacts are where Bird keeps your recipient data so you can group people into audiences and reuse them. You manage contacts in the dashboard's Contacts section, with the bird CLI, or through the contacts API.
Reaching contacts
Storing a contact does not send anything on its own. To email one person, send a normal message to their address with the send API; the contact record keeps who they are, ready to reuse. To reach many at once, send a batch; to build reusable groups, see audiences.
The Contacts page
The Contacts page (Contacts → All contacts in the sidebar) lists everyone in your workspace, with their email, name, external ID, and creation date. Search by email address to find one, click a row to open the contact, and use the header buttons to add a single contact or import many at once. Viewing contacts needs the email_marketing read permission; adding, editing, and deleting need write.

What a contact holds
Every contact has a workspace-unique email address and, optionally, a name and your own identifier for it:
| Field | What it is |
|---|---|
| The address, required and unique per workspace. Bird stores it trimmed and lowercased, so Sam@Acme.com and sam@acme.com are the same contact. | |
| first_name | Optional given name, used to personalize a send. |
| last_name | Optional family name. |
| external_id | Optional. Your own primary key for the person (a user ID from your database), unique per workspace when set. It is how you match a Bird contact back to your own records without relying on the email. |
| data | Custom property values, one per registered contact property. |
Each contact also carries a Bird ID with a con_ prefix and its created and updated timestamps. The full field contract is in the API reference.
Contact properties
Contact properties are the typed schema for the custom fields on a contact. You register a property once per workspace, and from then on every contact can carry a value for it under data. Registering the schema up front is what lets you personalize and segment reliably: a value is always the type you declared, not an untyped blob that varies per contact.

Manage them on the Contact properties page, reached from the sidebar or the Properties button on the Contacts page. Each property has a key, a type, and an optional fallback:
- Key is the name you reference the value by, for example plan_tier. It must be lowercase and start with a letter (^[a-z][a-z0-9_]*$), and it is fixed once created.
- Type is one of string, number, or boolean, and it is also fixed once created. Dates have no dedicated type; store a date as a string (an ISO 8601 date, say). A value written to a contact must match its property's declared type.
- Fallback value is the default used when a contact has no value for the property, so a missing plan_tier can read as free rather than a blank.
Properties are not deleted, they are archived. Archiving a property stops new writes to its key while keeping every value already stored, and the key stays reserved so it can never be reused for a different type. Unarchive it to bring it back. This is why the type is immutable: a stored number must never start being read as a string. A workspace can register up to 200 properties, and archived properties count toward that cap because their keys stay reserved.
You can set property values wherever you edit a contact: the dashboard's contact form shows one typed input per active property, and the CLI and API take the same keys under data.
Importing and syncing contacts
The dashboard import is the fastest way to load a list. From Import on the Contacts page, paste up to 1000 rows, one contact per line as email, first name, last name with only the email required. Bird matches each row to an existing contact by email and updates it, or creates it if it is new, so re-importing the same file is an upsert rather than a pile of duplicates. The result tells you how many were added, updated, and skipped.
To sync from your own database, script the CLI or call the batch endpoint directly. bird contacts create <email> adds one; bird contacts batch upserts up to 1,000 in a single call, which is how you keep Bird in step with your system: one batch per run rather than one request per person.
代码示例
bird contacts create alex@example.com --first-name Alex --last-name Rivera --external-id user_8412Batch entries are matched by email the same way as the dashboard import, can carry custom property values, and can drop every contact in the request straight into audiences via audience_ids. Each entry succeeds or fails on its own, and the response reports one result per entry in submission order:
代码示例
{
"data": [
{
"contact_id": "con_01ky7q5t51echr7mqj5c08423b",
"email": "alex@example.com",
"status": "updated"
},
{
"contact_id": "con_01ky7q6mxdfhe86c9dqyt866pz",
"email": "jamie@example.com",
"status": "created"
},
{
"email": "casey@example.com",
"error": {
"message": "A contact with this external_id already exists in this workspace.",
"type": "conflict_error"
},
"status": "failed"
}
]
}Two defaults worth knowing for syncs. A batch merges data keys onto what a contact already stores (set data_mode: "replace" to overwrite the whole map instead), so an import that touches one attribute never wipes the others. And set your own external_id on each contact, so a later sync can find the same person even if their email changes.
Deleting a contact
Deleting a contact is a hard delete: the record and its audience memberships go, and it is not recoverable. It does not touch suppressions, though. An address that unsubscribed or hard-bounced stays on your suppression list even after you delete the contact, so deleting someone never quietly makes them mailable again.
Next steps
- Audiences: group contacts into reusable lists
- Suppressions: the workspace list of addresses Bird will not deliver to, kept separate from your contacts
- Batch sending: reaching many recipients today, up to 100 messages per request
- CLI: scripting contacts, properties, and audiences with the bird command
- API reference: full request and response schemas