Contacts
Manage your audience with custom properties, suppression, and filtering.
Contacts are the foundation of Kraiter. Every email you send, every sequence enrolment, and every event is tied to a contact. A contact is uniquely identified by their email address within your tenant.
Creating a contact
Create a contact by providing an email address and optional properties.
const contact = await kraiter.contacts.create({
email: 'alice@example.com',
properties: {
name: 'Alice',
plan: 'pro',
signupDate: '2025-06-15',
},
});curl -X POST https://api.kraiter.com/api/contacts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"properties": {
"name": "Alice",
"plan": "pro",
"signupDate": "2025-06-15"
}
}'If a contact with the same email already exists, the request returns a CONTACT_EXISTS error (HTTP 409). Use update to modify an existing contact, or upsert (see below) for create-or-update semantics keyed by email.
Contact properties
Every contact has two categories of properties: system properties and custom properties.
System properties
System properties are managed automatically by Kraiter:
| Property | Type | Description |
|---|---|---|
email | string | The contact's email address (unique identifier) |
createdAt | date | When the contact was created |
updatedAt | date | When the contact was last modified |
subscribed | boolean | Whether the contact is opted in (false once they unsubscribe) |
suppressed | boolean | Whether the contact is suppressed |
suppressionReason | string | Why the contact is suppressed (bounce or complaint) — present only when suppressed is true |
suppressedAt | date | When the contact was suppressed — present only when suppressed is true |
Custom properties
Custom properties are key-value pairs you define. Each property has a type:
- string — Text values (e.g. name, plan, city)
- number — Numeric values (e.g. age, orderCount)
- boolean — True/false values (e.g. isVip, hasCompletedOnboarding)
- date — ISO 8601 date strings (e.g. signupDate, lastPurchase)
Properties are set when creating or updating a contact. You can use them in templates with Liquid variables, in segment rules, and in sequence conditions.
await kraiter.contacts.update('alice@example.com', {
properties: {
orderCount: 5,
isVip: true,
lastPurchase: '2025-11-20',
},
});Derived properties
Derived properties are computed automatically from a contact's engagement data. They live under the read-only derived object and cannot be set manually. The most useful fields are:
- derived.lastSendAt — When the last email was sent to this contact
- derived.lastOpenAt — When the contact last opened an email
- derived.lastClickAt — When the contact last clicked a link in an email
- derived.lastEventAt — When the contact last triggered any event
- derived.totalSends — Total number of emails sent to this contact
- derived.totalOpens — Total number of email opens
- derived.totalClicks — Total number of link clicks
- derived.openedLastSend / derived.clickedLastSend — Whether they engaged with the most recent send
- derived.inactiveDays — Days since the last recorded activity
Derived properties are available in segment rules (as derived conditions) and sequence conditions, making it easy to target engaged or inactive contacts.
Retrieving contacts
Contacts are addressed internally by contactId (a ULID). Fetch a contact by email with getByEmail, or by ID with get:
const contact = await kraiter.contacts.getByEmail('alice@example.com');
// or, if you already have the ID:
const same = await kraiter.contacts.get(contact.contactId);curl "https://api.kraiter.com/api/contacts?email=alice%40example.com" \
-H "Authorization: Bearer YOUR_API_KEY"Listing and filtering contacts
List contacts with pagination:
const { items, nextCursor } = await kraiter.contacts.list({
limit: 50,
});
// Fetch the next page
const nextPage = await kraiter.contacts.list({
limit: 50,
cursor: nextCursor,
});Contact statistics
Get tenant-wide contact counts without paging through the full list:
const stats = await kraiter.contacts.stats();
console.log(stats.total); // Total contacts
console.log(stats.suppressedCount); // Suppressed (bounced or complained)
console.log(stats.addedLast24h); // Added in the last 24 hours
console.log(stats.addedLast7d); // Added in the last 7 dayscurl https://api.kraiter.com/api/contacts/stats \
-H "Authorization: Bearer YOUR_API_KEY"Updating a contact
update takes the contactId, and only the properties you include are modified — existing properties are preserved. Resolve the ID by email first if you only have the address:
const contact = await kraiter.contacts.getByEmail('alice@example.com');
await kraiter.contacts.update(contact.contactId, {
properties: {
plan: 'enterprise',
},
});To remove a property, set its value to null:
await kraiter.contacts.update(contact.contactId, {
properties: {
temporaryFlag: null,
},
});If you key contacts by email and would rather skip the lookup, use upsert. It creates the contact when absent and merges properties into an existing one:
await kraiter.contacts.upsert({
email: 'alice@example.com',
properties: { plan: 'enterprise' },
});Deleting a contact
Delete a contact permanently. This removes all associated data including event history and sequence enrolments.
await kraiter.contacts.delete(contact.contactId);curl -X DELETE https://api.kraiter.com/api/contacts/CONTACT_ID \
-H "Authorization: Bearer YOUR_API_KEY"Suppression vs unsubscribe
It is important to understand the difference between these two states:
Unsubscribed
A contact unsubscribes when they explicitly opt out of marketing emails — typically by clicking the unsubscribe link in an email. Unsubscribed contacts:
- Will not receive sequence emails or campaign emails
- Can still receive transactional emails (if
ignoreUnsubscribeis set) - Can be resubscribed via the API
Suppressed
A contact is suppressed when Kraiter detects a delivery problem — a hard bounce or a spam complaint. Suppressed contacts:
- Will not receive any emails (including transactional)
- Cannot be resubscribed without first removing the suppression
- Have a
suppressionReasonindicating why (bounceorcomplaint)
This distinction ensures you respect both user preferences and technical delivery constraints. Attempting to send to a suppressed address would harm your sender reputation, so Kraiter blocks all sends to suppressed contacts.
const contact = await kraiter.contacts.getByEmail('alice@example.com');
if (contact.suppressed) {
console.log(`Suppressed: ${contact.suppressionReason}`);
} else if (!contact.subscribed) {
console.log('Contact has unsubscribed');
} else {
console.log('Contact is active');
}