Upsert a contact
addContact creates the contact, or — if it already exists — overwrites just the fields you provide:
const { contact, created, suppressed, optOuts } = await client.addContact({
email: 'user@example.com',
firstName: 'Ada',
tags: ['customer', 'beta'], // auto-created on first use
});
// Partial update: lastName and tags stay untouched
await client.updateContact({ email: 'user@example.com', firstName: 'Grace' });
// Tag management
await client.addTag({ email: 'user@example.com', tag: 'vip' });
await client.removeTag({ email: 'user@example.com', tag: 'beta' });
// Lookup and deletion
const { contact: found } = await client.getContact({ email: 'user@example.com' });
await client.deleteContact({ email: 'user@example.com' });Unsubscribe and resubscribe
When a user changes their email preferences in your product, record it in Ewyn so marketing sends stop. topic is a topic slug from Audience → Topics; omit it for all marketing email.
// Stop one topic
await client.unsubscribeContact({ email: 'user@example.com', topic: 'newsletter' });
// Stop all marketing email
await client.unsubscribeContact({ email: 'user@example.com' });
// Resume — only when the person asked you to
const { removed, optOuts } = await client.resubscribeContact({
email: 'user@example.com',
topic: 'newsletter',
});
// Every contact response lists the marketing opt-outs
const { optOuts: current } = await client.getContact({ email: 'user@example.com' });
// [{ topic: 'newsletter', createdAt: '...' }, { topic: null, createdAt: '...' }]
// topic: null = all marketingBoth are idempotent: created: false and removed: false report that nothing changed. An unknown or archived topic throws a 404 EwynApiError with code TOPIC_NOT_FOUND. Opt-outs never affect transactional sends — see Subscriptions and unsubscribe.
Method reference
| Method | Behavior | 404 if missing? |
|---|---|---|
| addContact | Upsert by email; merges provided fields, adds tags | no — creates |
| updateContact | Same merge semantics, but the contact must exist | yes |
| getContact | Fetch a contact with its tags | yes |
| deleteContact | Permanent delete; idempotent | no — reports deleted: false |
| addTag | Attach one tag (auto-created); idempotent | yes |
| removeTag | Detach one tag; idempotent | yes |
| unsubscribeContact | Opt the address out of one topic, or of all marketing; idempotent | no — opt-outs are per address (404 for an unknown topic) |
| resubscribeContact | Remove that opt-out; idempotent | no — reports removed: false (404 for an unknown topic) |
Semantics to know
- Merge, never erase.
addContact/updateContactonly overwrite fields you provide, and tags merge additively. The only way to remove a tag isremoveTag; the only way to remove a contact isdeleteContact. - Tags auto-create with a deterministic dashboard color. Names are case-sensitive —
VIPandvipare different tags. Max 50 tags per call, 100 chars each. - Emails are normalized server-side (trimmed, lowercased) before matching.
optOuts lists marketing opt-outs: a send() with type: 'marketing' under one of those topics (or any marketing send after an all-marketing opt-out) is rejected with 422, while transactional sends go through. suppressed: true means a delivery block — hard bounce, spam complaint, or manual suppression. Bounces and manual suppressions block every send, complaints block marketing only, and none of them can be removed through the API. You can still manage the contact's data either way.
Prefer raw HTTP? The same operations are exposed as REST endpoints — see the contacts API reference.