Audience
Contacts
A contact is a person on your list, keyed by email. Store names and arbitrary custom data, then use contacts to power segments, campaigns, and automations.
Everything in your audience is built around the contact. It's just an email plus whatever you want to know about the person behind it — and it's the anchor that ties events, sends, and automations together.
The contact model#
Contacts are keyed by their email address, which is unique within a project. A contact carries a small set of built-in fields plus a free-form data object for anything specific to your product.
Contact fields
emailstringRequirednamestringOptional{{first_name}} and other personalization.subscribedbooleanOptionaltrue. Unsubscribing (or a complaint) flips this to false.sourcestringOptionalapi, import, or a value you set yourself.dataobjectOptionalUnlimited contacts
There's no cap on how many contacts you store, and no per-contact fee. Deckle bills on what you send, not on the size of your list — so you can keep every address, subscribed or not.Create or update#
Contacts are upserted by email: create a contact that already exists and Deckle updates it instead of erroring. The response status tells you which happened — 201 when a new contact was created, 200 when an existing one was updated.
import { Deckle } from "@deckle/sdk";
const deckle = new Deckle(process.env.DECKLE_API_KEY!);
const contact = await deckle.contacts.create({
email: "jane@example.com",
name: "Jane Doe",
subscribed: true,
source: "signup-form",
data: {
plan: "pro",
company: "Example Inc.",
},
});
// contact.id -> "contact_..."Upsert semantics
Because create is an upsert onemail, it's safe to call on every signup or sync — you won't create duplicates. Only the fields you pass are changed; omitted fields keep their previous values.List, get, update, delete#
The full set of contact operations is available from the SDK and the REST API. Listing is cursor-based — pass limit and cursor to page through results.
import { Deckle } from "@deckle/sdk";
const deckle = new Deckle(process.env.DECKLE_API_KEY!);
// List (cursor-based pagination)
const { data, has_more, next_cursor } = await deckle.contacts.list({
limit: 50,
});
// Get a single contact by id or email
const contact = await deckle.contacts.get("jane@example.com");
// Update — merge in new fields
await deckle.contacts.update("jane@example.com", {
data: { plan: "enterprise" },
});
// Delete
await deckle.contacts.delete("jane@example.com");Prefer to work over HTTP directly? Each operation maps to a REST endpoint — see the Contacts API reference for request and response shapes.
Custom data#
The data object holds arbitrary JSON. Store plan tiers, company names, feature flags, lifecycle stages — whatever your product cares about. There's no schema to define up front; just send the keys you want.
{
"plan": "pro",
"company": "Example Inc.",
"signup_date": "2026-05-01",
"trial_ends": "2026-05-15",
"seats": 12,
"beta_features": ["reports", "automations"]
}Custom data is what makes the rest of the platform expressive. It feeds {{variable}} personalization in templates and subject lines, and it's available as attributes when you build segments and branch inside automations.
Personalize with data
Any key ondata can be referenced in a template with a fallback, e.g. {{plan | "free"}}. See Variables & personalization for the full syntax.Import & export#
Already have a list elsewhere? Import it from the dashboard, or add contacts one at a time through the API. You can export at any time.
Open Audience → Contacts
Upload your CSV
Map your fields
email, name, subscribed — or map it into data as a custom field. Rows are upserted by email, so re-importing updates existing contacts rather than duplicating them.Export when you need to
Automations#
Contacts don't just sit in a table — they trigger workflows. When a new contact is created as subscribed, Deckle looks for live automations that fire on the contact.subscribed trigger and enrolls the contact automatically.
Auto-enroll on subscribe
Newly-created subscribed contacts are automatically enrolled in any live welcome automation triggered bycontact.subscribed. Draft automations don't enroll anyone — set an automation to live to start welcoming new contacts.The contact detail page also shows an activity timeline — sends, opens, clicks, and events — so you can see a contact's full history in one place.
Reasons a contact might not be subscribed#
A contact's subscribed flag flips to false in a few cases:
| Cause | Effect |
|---|---|
| Unsubscribe (one-click or hosted page) | Contact is unsubscribed and added to suppressions |
| Complaint (marked as spam) | Auto-suppressed via SES/SNS; no further marketing email |
Imported with subscribed: false | Stored, but excluded from marketing sends and auto-enroll |