Kraiter
Guides

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.

SDK
const contact = await kraiter.contacts.create({
  email: 'alice@example.com',
  properties: {
    name: 'Alice',
    plan: 'pro',
    signupDate: '2025-06-15',
  },
});
cURL
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:

PropertyTypeDescription
emailstringThe contact's email address (unique identifier)
createdAtdateWhen the contact was created
updatedAtdateWhen the contact was last modified
subscribedbooleanWhether the contact is opted in (false once they unsubscribe)
suppressedbooleanWhether the contact is suppressed
suppressionReasonstringWhy the contact is suppressed (bounce or complaint) — present only when suppressed is true
suppressedAtdateWhen 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.

SDK
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:

SDK
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
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:

SDK
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:

SDK
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 days
cURL
curl 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:

SDK
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:

SDK
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:

SDK
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.

SDK
await kraiter.contacts.delete(contact.contactId);
cURL
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 ignoreUnsubscribe is 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 suppressionReason indicating why (bounce or complaint)

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.

Check contact status
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');
}