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