Enrollment & triggers
Enroll contacts into an active sequence, the enrollment lifecycle and the real state of triggers.
A contact enters a sequence through an enrollment. Each enrollment carries its own position and status, independent of the rest.
Enroll a contact
POST /sequences/{id}/enroll
Authorization: Bearer <apiKey or accessToken>
X-Tenant-Id: {tenantId}
Content-Type: application/json
{
"contactId": "cont_123"
}
Requires the sequences:write scope. For the enrollment to succeed:
- The sequence must be
ACTIVE. - The contact must belong to your tenant.
- It must be opted in and not on the suppression list.
- It cannot already be enrolled in that sequence (one enrollment per contact and sequence).
Enrollment lifecycle
| Status | Meaning |
|---|---|
ACTIVE | Moving through the steps |
COMPLETED | Reached the end of the sequence |
CANCELLED | Stopped: the contact unsubscribed or was suppressed, or the recipient was rejected |
PAUSED | The sequence was set to PAUSED |
FAILED | The send exhausted its retries, or the template/sequence is missing |
Exits happen on their own: if the contact is suppressed or unsubscribes mid-flow, its enrollment moves to CANCELLED and receives no further steps. Pausing the sequence moves in-flight enrollments to PAUSED.
List enrollments
GET /sequences/{id}/enrollments?page=1&limit=20&status=ACTIVE
Requires sequences:read. Returns paginated enrollments; filter by status with any of the values in the table above.
Automatic triggers
When you create the sequence you set a triggerType and its triggerConfig. Beyond manual enrollment, all three automatic triggers now enroll contacts on their own.
triggerType | triggerConfig | When it enrolls |
|---|---|---|
MANUAL | — | Only via POST /sequences/:id/enroll |
CONTACT_CREATED | { "requireOptIn": true } | When an opted-in contact is created |
CONTACT_TAGGED | { "tag": "vip" } | When that tag is added to a contact |
API_EVENT | { "eventName": "signup" } | When that event is received via the API |
CONTACT_CREATED
Creating an opted-in contact (via PUT /contacts or the SDK) enrolls it into every ACTIVE sequence with this trigger. The fan-out is asynchronous (the sequence-trigger queue), so a bulk import does not slow the request.
CONTACT_TAGGED
Tagging a contact with the configured tag enrolls it into the ACTIVE sequences waiting for it:
POST /contacts/{idOrExternalId}/tags
Authorization: Bearer <apiKey or accessToken>
X-Tenant-Id: {tenantId}
Content-Type: application/json
{
"tags": ["vip"]
}
Requires contacts:write. It only fires the first time the tag is added (idempotent: re-tagging does not re-enroll). Removing the tag with DELETE /contacts/{idOrExternalId}/tags/{tag} does not cancel an enrollment already created. Enrollment is asynchronous too.
API_EVENT
Fire your own event; the sequences whose triggerConfig.eventName matches enroll the contact:
POST /sequences/events
Authorization: Bearer <apiKey or accessToken>
X-Tenant-Id: {tenantId}
Content-Type: application/json
{
"eventName": "signup",
"contactId": "cont_123"
}
Requires sequences:write. It responds { "matched": N, "enrolled": M }: how many sequences matched and how many actually enrolled (already-enrolled or non-opted-in contacts are skipped). Unlike the other two, it is synchronous.