resend/references/contacts.md
Version edbfece3f402.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
Contacts
Overview
Contacts represent email recipients stored in Resend. They support custom properties, segment assignment, and topic subscriptions for managing audiences and preferences.
SDK Methods
Node.js
| Operation | Method | Notes |
|---|---|---|
| Create | resend.contacts.create(params) |
Add a contact with properties, segments, topics |
| Get | resend.contacts.get({ id }) or resend.contacts.get({ email }) |
Lookup by ID or email |
| List | resend.contacts.list({ limit?, offset?, segmentId? }) |
Filter by segment |
| Update | resend.contacts.update(params) |
By id or email |
| Delete | resend.contacts.remove({ id }) or resend.contacts.remove({ email }) |
Not .delete() |
Python
resend.Contacts.create/get/list/update/remove — same operations with snake_case params (e.g., first_name, last_name, segment_id).
Create Contact
const { data, error } = await resend.contacts.create({
email: '[email protected]',
firstName: 'Alice',
lastName: 'Smith',
unsubscribed: false,
properties: {
plan: 'enterprise',
company: 'Acme Corp',
signupDate: '2026-01-15',
},
segments: [{ id: 'seg_abc123' }],
topics: [
{ id: 'topic_product_updates', subscription: 'opt_in' },
{ id: 'topic_marketing', subscription: 'opt_out' },
],
});
if (error) {
console.error(error);
return;
}
console.log(data.id); // contact ID
contact = resend.Contacts.create({
"email": "[email protected]",
"first_name": "Alice",
"last_name": "Smith",
"unsubscribed": False,
"properties": {
"plan": "enterprise",
"company": "Acme Corp",
},
})
# Add to segment separately (Python SDK doesn't support segments/topics on create)
resend.Contacts.Segments.add({"contact_id": contact["id"], "segment_id": "seg_abc123"})
Get and Update
// Get by email (alternative: pass { id: 'contact_uuid' })
const { data, error } = await resend.contacts.get({ email: '[email protected]' });
// Update by email — change properties, set a property to null to delete it
const { data: updated, error: updateErr } = await resend.contacts.update({
email: '[email protected]',
firstName: 'Alicia',
properties: {
plan: 'pro', // update existing property
company: null, // delete this property
},
});
Delete and List
// Delete by ID or email — pick one
const { data, error } = await resend.contacts.remove({ email: '[email protected]' });
// List with segment filter
const { data: contacts, error: listErr } = await resend.contacts.list({
segmentId: 'seg_abc123',
limit: 50,
});
Bulk Import from CSV
Import many contacts at once from a CSV file (POST /contacts/imports, multipart/form-data). Imports run asynchronously: create returns an import id immediately, then poll get until status is completed (or failed).
SDK Methods (Node.js)
| Operation | Method | Notes |
|---|---|---|
| Create | resend.contacts.imports.create({ file, columnMap?, onConflict?, segments?, topics? }) |
file is a Blob/File (CSV). Returns { id } |
| Get | resend.contacts.imports.get(id) |
Status + counts. id is a positional string — not { id } |
| List | resend.contacts.imports.list({ limit?, after?, before?, status? }) |
status filters by queued / in_progress / completed / failed |
import { readFile } from 'node:fs/promises';
const file = new Blob([await readFile('contacts.csv')], { type: 'text/csv' });
const { data, error } = await resend.contacts.imports.create({
file,
// 'upsert' (default) updates existing contacts; 'skip' leaves them unchanged
onConflict: 'upsert',
segments: [{ id: 'seg_abc123' }],
});
if (error) {
console.error(error);
return;
}
// Imports are async — poll until processing finishes
const { data: imp } = await resend.contacts.imports.get(data.id);
console.log(imp.status, imp.counts);
// e.g. 'completed' { total: 1200, created: 800, updated: 300, skipped: 75, failed: 25 }
// List past imports, newest first; filter by status and paginate with after/before
const { data: imports } = await resend.contacts.imports.list({ status: 'completed', limit: 20 });
CSV columns
Without columnMap, columns are matched by the lowercase names email (required), first_name, last_name, unsubscribed — matching is case-sensitive, so a CSV with Email / First Name headers returns 422 validation_error ("CSV missing required email column"). Map non-standard headers explicitly:
await resend.contacts.imports.create({
file,
columnMap: {
email: 'Email',
firstName: 'First Name',
lastName: 'Last Name',
properties: { plan: { column: 'Plan', type: 'string' } },
},
});
The SDK accepts camelCase (
firstName,lastName); the rawmultipart/form-datafield iscolumn_mapwith snake_case keys (first_name,last_name).column_map,segments, andtopicsare sent as JSON-encoded strings. Maximum file size is 100 MB.
Properties that aren't already defined on your contacts are created automatically when you map them in the import; no need to pre-register them.
Raw HTTP
curl -X POST 'https://api.resend.com/contacts/imports' \
-H 'Authorization: Bearer re_xxxxxxxxx' \
-F '[email protected];type=text/csv' \
-F 'column_map={"email":"Email","first_name":"First Name"}' \
-F 'on_conflict=upsert'
Common Mistakes
| Mistake | Fix |
|---|---|
Passing both id and email to get/update/remove |
Use one or the other — not both |
Using audienceId (Node.js) |
Segments replaced audiences — use segmentId. Python SDK still uses audience_id in create params |
Calling .delete() |
SDK method is .remove() |
| Expecting property deletion with empty string | Set property value to null to delete it |
Not checking error in Node.js |
SDK returns { data, error }, does not throw — always destructure and check |
Forgetting email is required on create |
email is the only required field — all others are optional |
| CSV import returns 422 "missing required email column" | Column matching is case-sensitive lowercase (email, first_name, last_name) — pass columnMap for headers like Email / First Name |
Calling imports.get({ id }) |
imports.get(id) takes a positional string, unlike contacts.get({ id }) |
| Treating an import as synchronous | create returns an id immediately — poll imports.get(id) until status is completed |