DeckleDocs
Dashboard

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

emailstringRequired
The contact's email address and unique key within the project.
namestringOptional
Optional display name, used for {{first_name}} and other personalization.
subscribedbooleanOptional
Whether the contact is subscribed to marketing email. Defaults to true. Unsubscribing (or a complaint) flips this to false.
sourcestringOptional
Where the contact came from, e.g. api, import, or a value you set yourself.
dataobjectOptional
Arbitrary JSON — any custom fields you want to store on the contact.

Unlimited 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 on email, 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.

contacts.ts
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.

json
{
  "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 on data 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

In the dashboard top-nav, go to Audience and open the Contacts table. Choose Import.

Upload your CSV

Drop in a CSV file. The first row should be your column headers.

Map your fields

Match each CSV column to a contact field — 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

Use Export on the Contacts table to download your list as a CSV, including custom data fields.

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 by contact.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:

CauseEffect
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: falseStored, but excluded from marketing sends and auto-enroll

Next steps#