Concepts

Contacts & events

The people you send to and the things they do.

Contacts

A contact is a person in a project, identified by email (and optionally your own external_id). Contacts carry custom attributes — arbitrary key/values you set — which you can reference in templates and filter on in automations.

Upserts shallow-merge attributes: sending { "plan": "Pro" } updates plan and leaves other attributes untouched.

curl https://sendandretain.com/api/v1/contacts \
  -H "Authorization: Bearer $AEM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "attributes": { "plan": "Pro" } }'

A timezone attribute (IANA, e.g. America/New_York) controls automation send windows for that contact.

Events

An event is something a contact did — order.completed, trial.started. You emit events through the API and get 202 Accepted as soon as we have durably stored it; active automations whose trigger and filter match enroll the contact a moment later, in the background.

That means this call is safe to make from inside your own request — a checkout, a booking confirmation — because it never makes you wait on our work. To see what an event actually did, read the contact's timeline with GET /api/v1/contacts/{id}/events: each event carries the automations it enrolled and the reasons the others declined. Send a dedupe_key and a retry after a network timeout is always safe.

curl https://sendandretain.com/api/v1/events \
  -H "Authorization: Bearer $AEM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "name": "order.completed", "properties": { "total": 42.5 } }'

Emitting an event with an unknown email creates the contact. You can also pass contact_attributes to upsert attributes in the same call, and a dedupe_key to make the event idempotent.

Event properties and contact attributes are both available to automation filters — that's how you target "trial started on the Pro plan".

On this page