Contacts, lists and segments
Marketing contacts with typed properties, private and public lists, live filter segments, companies, and CSV import and export, compatible with Resend contacts.
A contact is one email address in a project. Lists are groups contacts join or leave; segments are saved filters that always reflect the current data. Campaigns go to lists, a segment, or both.
Contacts
POST /contacts creates a contact. The address is the contact's identity: it is stored lowercased, and every contact path accepts the ct_… id or the address.
curl https://api.stamp0.com/contacts \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "maya@northwind.dev",
"first_name": "Maya",
"properties": { "plan": "pro", "seats": 12 },
"lists": [{ "id": "lst_…" }]
}'- Creating an address that exists answers
409.POST /contacts/batchcreates or updates up to 1,000 contacts by address, each succeeding or failing on its own. PATCH /contacts/:idmergespropertiesinto the stored ones; anullvalue removes that property.unsubscribed: trueopts the contact out of all marketing email. List opt-outs are per list (below).- A
listsentry without astatus(and Resendsegmentsandaudience_id, and adding a contact to a segment or audience) subscribes the contact unless they opted out of that list or are waiting to confirm a double opt-in: a sync never resubscribes someone who unsubscribed. Re-subscribing someone who opted out ("status": "subscribed"over an unsubscribed membership, a topic back toopt_in, or"unsubscribed": falseon an unsubscribed contact) is refused with a 403 for API keys and MCP clients: only the contact (preference center, a double opt-in confirmation) or an owner or admin in the dashboard can do it. To ask the contact to come back, add them to a double opt-in list aspending; they get a confirmation email. external_idholds your own user ID.sourcerecords where the contact came from:api,dashboard,import,formorsequence.GET /contactslists newest first;searchmatches an exact address or part of an address or name, andlist_idnarrows to a list's members.GET /contacts/:id/activityis the contact's timeline: emails sent to it, custom events, list changes and its creation.
Every contact counts toward the plan's contact limit (see plan limits).
Properties
Properties are typed custom fields. Define them before you set them:
curl https://api.stamp0.com/properties \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "key": "plan", "type": "string", "fallback_value": "free" }'- Types are
string,number,booleananddate(dates are stored as ISO 8601 UTC). The key and type cannot change once created. - Keys use letters, digits and
_, start with a letter, and cannot shadow a built-in field such asemailorfirst_name. - A contact holds up to 100 property values; strings up to 2,000 characters.
- In templates a property is
{{ contact.plan }};fallback_valuefills it when a contact has no value. - Deleting a property removes its values from every contact.
/contact-properties is the same resource on Resend's path.
Lists
A list is a group contacts belong to, with a status per member: subscribed, unsubscribed or pending (waiting for double opt-in).
curl https://api.stamp0.com/lists \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Product updates", "visibility": "public", "double_opt_in": true }'| Field | Values |
|---|---|
visibility | private (default): you manage membership. public: shown in the preference center, where contacts subscribe and unsubscribe themselves |
default_subscription | opt_in (default): only members who subscribed receive it. opt_out (public lists only): every contact counts as subscribed until they opt out |
double_opt_in | New members stay pending until they click the confirmation email |
POST /lists/:id/contactsadds up to 1,000 contacts by id or address, creating the ones that do not exist. Addresses are checked likePOST /contacts: one that is not a bare address of at most 254 characters refuses the whole request (422), and an unknown id is reported per item. On a double opt-in list (or withstatus: "pending") a confirmation email goes out from the project'sdefault_from.DELETE /lists/:id/contacts/:contactremoves a member. On anopt_outlist it records an opt-out instead, since no row there means subscribed.PATCH /contacts/:id/listssets a contact's status on several lists at once;GET /contacts/:id/listsreads them.
Public lists replace Resend's topics, and private lists are Resend's manually managed segments and legacy audiences. See Resend aliases.
Segments
A segment is a filter, not a membership list: it matches whoever fits the filter at the moment it is used. A campaign resolves it when sending starts; a sequence triggered by segment_entered checks it every few minutes.
curl https://api.stamp0.com/segments \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Active Pro customers",
"filter": {
"and": [
{ "type": "property", "key": "plan", "operator": "eq", "value": "pro" },
{ "type": "event", "name": "app.opened", "operator": "performed", "within_days": 30 },
{ "not": { "type": "list", "list_id": "lst_…", "operator": "in" } }
]
}
}'A filter is a tree of and, or and not groups over conditions:
| Condition | Tests | Operators |
|---|---|---|
contact | email, email_domain, first_name, last_name, source, unsubscribed, created_at, updated_at | By type, below |
property | A custom property, by key | By the property's type |
list | Membership of list_id (with status, default subscribed) | in, not_in |
company | Any of the contact's companies: field (name, domain) or a company property key | By value |
event | A custom event name, optionally count times and within_days | performed, not_performed |
| Type | Operators |
|---|---|
| String | eq, neq, contains, not_contains, starts_with, ends_with, in, not_in, is_set, is_not_set (case-insensitive) |
| Number | eq, neq, gt, gte, lt, lte, between, is_set, is_not_set |
| Boolean | eq, is_set, is_not_set |
| Date | before, after, between, within_last_days, more_than_days_ago, is_set, is_not_set |
- Negative operators (
neq,not_contains,not_in) also match contacts without a value. - Filters nest up to 6 levels with up to 50 conditions;
inlists hold up to 100 values.{ "and": [] }matches every contact. POST /segments/previewcounts a filter's matches and returns a sample, without saving it.GET /segments/:id/previewcounts a saved segment now (and a sample); thecontacts_counton the segment is cached.GET /segments/:id/contactslists a segment's contacts, paged likeGET /contacts(Resend'scontacts.list({ segmentId })).
/segments also serves Resend's manually managed segments, which are private lists: a create without a filter makes a list (lst_…), and GET /segments returns both kinds with kind: "filter" or "manual".
Companies
Companies group contacts for B2B sending: name, domain, external_id and their own properties. Attach contacts with POST /companies/:id/contacts (by id or address, with an optional role), and filter on company fields in segments.
POST /group is the Segment-style call: it upserts the company by group_id (its external_id) with traits, and attaches the contact named by email or contact_id.
curl https://api.stamp0.com/group \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "group_id": "org_42", "traits": { "name": "Northwind", "domain": "northwind.dev", "seats": 40 }, "email": "maya@northwind.dev", "role": "admin" }'Import and export
Import a CSV of up to 50 MB by sending the file as the request body. consent is required: a short statement of how these people agreed to receive marketing email, kept with the import.
curl "https://api.stamp0.com/contacts/imports?consent=Signed%20up%20at%20acme.co&list_ids=lst_…" \
-H "Authorization: Bearer $STAMP0_API_KEY" \
-H "Content-Type: text/csv" \
--data-binary @contacts.csv- The file needs an email column (
email,Email Address,mail).first_name,last_name,unsubscribed,external_idand columns named like a property key are mapped; anything else is listed as ignored. - Commas, semicolons and tabs are detected. Existing contacts are updated unless
update_existing=false; empty cells never clear stored values. - Imports run in the background. Poll
GET /contacts/imports/:idfor counters, read refused rows at/contacts/imports/:id/errors(or/errors/downloadas CSV), and stop one with/cancel.
GET /contacts/export streams every contact as CSV, one column per property; pass list_id or segment_id to export a list's members or a segment's matches. Text cells are always quoted, and text a spreadsheet could run as a formula (starting with =, +, - or @, also after a ;, tab or line break inside the cell) gets a ' in front of it; numbers and booleans are written as is.
Reference: Contacts, Properties, Lists, Segments, Companies.
Marketing email
Contacts, lists, segments, templates, campaigns, sequences and signup forms on Amazon SES in your own AWS account, with one-click unsubscribe built in.
Templates
Versioned HTML email templates with merge tags, variables, fallbacks and a preheader, compatible with Resend templates, used by campaigns, sequences and POST /emails.