Contacts
Manage your recipients with the Contacts API. Create, update, search, import via CSV, export, and organize contacts into segments for targeted broadcasts.
Per-topic preferences live under /subscription-topics/contacts/:contactId/subscriptions (see Subscription Topics); the dashboard's contact page shows every topic with a toggle. Contacts created through a signup form also get any submitted field that matches one of your contact properties.
/contactsCreate a new contact. The email address must be unique within your account.
{
"email": "alice@example.com",
"first_name": "Alice",
"last_name": "Smith",
"properties": {
"plan": "pro",
"signed_up": "2026-01-15"
},
"unsubscribed": false
}/contactsList contacts with pagination (per_page up to 100). Supports search by email or name, filtering by segment (segment_id, the seg_… public id), unsubscribed=true|false, and engagement_segment=active|at_risk|dormant.
{
"data": [
{
"id": 9120,
"publicId": "con_abc123def456ghi789",
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Smith",
"properties": { "plan": "pro" },
"unsubscribed": false,
"createdAt": "2026-03-23T10:00:00.000Z"
}
],
"meta": {
"page": 1,
"perPage": 20,
"total": 1250,
"totalPages": 63
}
}/contacts/:idRetrieve a single contact by ID, including all properties and subscription status.
{
"contact": {
"id": 9120,
"publicId": "con_abc123def456ghi789",
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Smith",
"properties": {
"plan": "pro",
"signed_up": "2026-01-15"
},
"unsubscribed": false,
"createdAt": "2026-03-23T10:00:00.000Z",
"updatedAt": "2026-03-23T12:00:00.000Z"
}
}/contacts/:idUpdate a contact's details. Only provided fields are updated.
{
"first_name": "Alice",
"properties": {
"plan": "enterprise",
"upgraded_at": "2026-03-23"
}
}/contacts/:idPermanently delete a contact and remove them from all segments.
{
"success": true
}/contacts/:id/unsubscribeUnsubscribe a contact from all future emails. They will still exist in your contact list but will not receive broadcasts.
{
"contact": {
"id": 9120,
"publicId": "con_abc123def456ghi789",
"email": "alice@example.com",
"unsubscribed": true,
"updatedAt": "2026-03-23T15:00:00.000Z"
}
}Bulk Import
Import up to 10,000 contacts per request with POST /contacts/import. The body is JSON — a contacts array where each entry needs an email and may carry first_name, last_name and string-valued properties. Addresses that already exist are skipped, not updated — use PATCH /contacts/:id to change an existing contact. To import a CSV file, parse it on your side (or use the CSV upload in the dashboard) and send the rows. Limited to 10 requests per minute.
curl -X POST https://api.poststack.dev/contacts/import \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "email": "alice@example.com", "first_name": "Alice" },
{ "email": "bob@example.com", "properties": { "plan": "pro" } }
]
}'const result = await poststack.contacts.import({
contacts: [
{ email: 'alice@example.com', first_name: 'Alice' },
{ email: 'bob@example.com', properties: { plan: 'pro' } },
],
});
// { imported: 2, skipped: 0, errors: [] }
// errors lists rows dropped for a property that failed validation.Export
Export contacts as a CSV file. Pass segment_id (a segment's public id) to export only that segment's members — it works for both rule-based and manual segments. An unknown segment id returns 404 rather than falling back to the full list.
// Export all contacts
const csv = await poststack.contacts.exportCsv();# Whole list
curl "https://api.poststack.dev/contacts/export" \
-H "Authorization: Bearer sk_live_..." \
-o contacts.csv
# One segment
curl "https://api.poststack.dev/contacts/export?segment_id=seg_abc123def456" \
-H "Authorization: Bearer sk_live_..." \
-o eu-beta.csvCustom Properties
Store arbitrary key-value data on contacts using the properties field. Property values can be used in template variables, segment rules, and workflow conditions:
const contact = await poststack.contacts.create({
email: 'alice@example.com',
first_name: 'Alice',
properties: {
plan: 'enterprise',
company: 'Acme Inc',
signup_source: 'website',
lifetime_value: '4500',
},
});
// Use properties in dynamic segments
await poststack.segments.create({
name: 'Enterprise Users',
rules: {
operator: 'and',
conditions: [
{ field: 'properties.plan', comparator: 'equals', value: 'enterprise' },
],
},
});