Events
The Events endpoints.
Auth: API key · Base URL:
https://api.mailkite.dev
Endpoints
Each row expands to its request and response schema. The address bar follows along, so any endpoint can be linked to directly.
POST /v1/events Record one application-level fact about a user — `user.created`, `trial.expiring`, `payment.failed`. THE primary way a sequence starts on a developer platform: your application already knows when a payment failed, so it says so, and every enabled trigger listening for that name enrolls the contact with the payload as its input. Identify the subject with `email` or `contactId` (never both). Pass a `dedupeKey` to make retries idempotent: a repeat returns the original event with `duplicate: true` rather than enrolling anyone twice.
Record one application-level fact about a user — `user.created`, `trial.expiring`, `payment.failed`. THE primary way a sequence starts on a developer platform: your application already knows when a payment failed, so it says so, and every enabled trigger listening for that name enrolls the contact with the payload as its input. Identify the subject with `email` or `contactId` (never both). Pass a `dedupeKey` to make retries idempotent: a repeat returns the original event with `duplicate: true` rather than enrolling anyone twice.
Request body
send-event-request.jsonOne application-level fact about one of your users — `user.created`, `trial.expiring`, `payment.failed`. This is the generic trigger for sequences: a sequence whose trigger names this event enrolls the contact when it arrives, and an enrollment parked on `wait_for_event` wakes on it. Distinct from the delivery timeline at GET /api/events, which records what MailKite did with a message; this records what YOUR user did.
| Field | Type | Notes |
|---|---|---|
| name req | string | Event name: 1-64 characters of letters, digits, dot, dash, or underscore, starting alphanumeric. Names beginning `mailkite.`, `mailkite:`, or `email.` are reserved for platform events. |
| event | string | Alias for `name`, accepted so a payload written for another provider works unchanged. `name` wins when both are given. |
| string | The address this event is about. Give either this or `contactId`, never both. An address we don't hold a contact for still records — the event is an observation, not a subscription, so no contact is created as a side effect. | |
| contactId | string | A contact you own (ctc_…), as an alternative to `email`. Give either this or `email`, never both. |
| payload | object | Free-form context carried with the event. Sequence steps read it as {{event.*}} for merge tags and, from conditions on, for branching. |
| dedupeKey | string | Idempotency key. A second POST with the same key returns the ORIGINAL event and `duplicate: true` instead of recording a second one — so a retried webhook can never enroll the same contact twice. |
The smallest body that makes this call — every other field is optional.
{
"name": "trial.expiring",
"email": "ada@example.com"
} {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://api.mailkite.dev/v1/schemas/send-event-request.json",
"title": "Send event request body",
"description": "One application-level fact about one of your users — `user.created`, `trial.expiring`, `payment.failed`. This is the generic trigger for sequences: a sequence whose trigger names this event enrolls the contact when it arrives, and an enrollment parked on `wait_for_event` wakes on it. Distinct from the delivery timeline at GET /api/events, which records what MailKite did with a message; this records what YOUR user did.",
"type": "object",
"required": [
"name"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Event name: 1-64 characters of letters, digits, dot, dash, or underscore, starting alphanumeric. Names beginning `mailkite.`, `mailkite:`, or `email.` are reserved for platform events.",
"examples": [
"trial.expiring",
"payment.failed"
]
},
"event": {
"type": "string",
"description": "Alias for `name`, accepted so a payload written for another provider works unchanged. `name` wins when both are given."
},
"email": {
"type": "string",
"description": "The address this event is about. Give either this or `contactId`, never both. An address we don't hold a contact for still records — the event is an observation, not a subscription, so no contact is created as a side effect."
},
"contactId": {
"type": "string",
"description": "A contact you own (ctc_…), as an alternative to `email`. Give either this or `email`, never both.",
"examples": [
"ctc_8Rt5NmZx"
]
},
"payload": {
"type": "object",
"description": "Free-form context carried with the event. Sequence steps read it as {{event.*}} for merge tags and, from conditions on, for branching.",
"examples": [
{
"plan": "pro",
"amountDue": 4200
}
]
},
"dedupeKey": {
"type": "string",
"description": "Idempotency key. A second POST with the same key returns the ORIGINAL event and `duplicate: true` instead of recording a second one — so a retried webhook can never enroll the same contact twice."
}
}
} Response body
send-event-response.jsonAcknowledgement that the event was recorded. Answered 202: the row is written synchronously, but everything the event sets in motion (sequence enrollment, waking a parked enrollment) is not.
| Field | Type | Notes |
|---|---|---|
| id req | string | Event id (cev_…). |
| object req | string | Always "event". |
| name req | string | The recorded event name. |
| email req | string | The address the event is about, normalized to lowercase. |
| duplicate req | boolean | true when a `dedupeKey` matched an event already recorded — nothing new was written and `id` is the original's. |
An actual response, recorded from the conformance suite.
{
"id": "cev_4b1c9e77",
"object": "event",
"name": "trial.expiring",
"email": "ada@example.com",
"duplicate": false
} {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://api.mailkite.dev/v1/schemas/send-event-response.json",
"title": "Send event response body",
"description": "Acknowledgement that the event was recorded. Answered 202: the row is written synchronously, but everything the event sets in motion (sequence enrollment, waking a parked enrollment) is not.",
"type": "object",
"required": [
"id",
"object",
"name",
"email",
"duplicate"
],
"properties": {
"id": {
"type": "string",
"description": "Event id (cev_…).",
"examples": [
"cev_4b1c9e77"
]
},
"object": {
"type": "string",
"description": "Always \"event\".",
"examples": [
"event"
]
},
"name": {
"type": "string",
"description": "The recorded event name.",
"examples": [
"trial.expiring"
]
},
"email": {
"type": "string",
"description": "The address the event is about, normalized to lowercase.",
"examples": [
"ada@example.com"
]
},
"duplicate": {
"type": "boolean",
"description": "true when a `dedupeKey` matched an event already recorded — nothing new was written and `id` is the original's."
}
}
} GET /v1/events List recorded events, newest first — the surface for confirming a POST landed and for debugging a sequence that did not trigger. Filter by `name` and/or `email`.
List recorded events, newest first — the surface for confirming a POST landed and for debugging a sequence that did not trigger. Filter by `name` and/or `email`.
Response body
list-events-response.json| Field | Type | Notes |
|---|---|---|
| events req | custom-event[] | Recorded events, newest first. |
| events[].id req | string | Event id (cev_…). |
| events[].name req | string | Event name as posted. |
| events[].email req | string | The address the event is about, normalized to lowercase. |
| events[].contactId | string · null | The contact this resolved to, when the address is one we hold. |
| events[].payload | object · null | The context posted with the event. |
| events[].dedupeKey | string · null | The idempotency key posted with the event. |
| events[].createdAt req | number | When the event was recorded, in ms epoch. |
An actual response, recorded from the conformance suite.
{
"events": [
{
"id": "cev_4b1c9e77",
"name": "trial.expiring",
"email": "ada@example.com",
"contactId": "ctc_8Rt5NmZx",
"payload": null,
"dedupeKey": null,
"createdAt": 1799999999000
}
]
} {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://api.mailkite.dev/v1/schemas/list-events-response.json",
"title": "List events response body",
"type": "object",
"required": [
"events"
],
"properties": {
"events": {
"type": "array",
"description": "Recorded events, newest first.",
"items": {
"$ref": "custom-event.json"
}
}
}
} GET /v1/events/names List the distinct event names this account works with, so an editor can offer them instead of asking you to remember one. Returns both events actually posted (with a count and when one last arrived) and events your sequences already trigger on or wait for but that may never have been sent — sequences are routinely written before the application emits the event, so a list of only-what-you-have-sent would be a trap.
List the distinct event names this account works with, so an editor can offer them instead of asking you to remember one. Returns both events actually posted (with a count and when one last arrived) and events your sequences already trigger on or wait for but that may never have been sent — sequences are routinely written before the application emits the event, so a list of only-what-you-have-sent would be a trap.
Response body
list-event-names-response.json| Field | Type | Notes |
|---|---|---|
| events req | event-name[] | Most-recently-seen first, then names only a sequence declares (alphabetical — they have no recency to sort by). |
| events[].name req | string | |
| events[].count req | number | How many of these have been recorded. 0 for a name only a sequence declares. |
| events[].lastSeenAt | number · null | When one last arrived, in ms epoch. Null if never. |
| events[].usedBy req | string[] | Names of the sequences that trigger on or wait for this event. |
An actual response, recorded from the conformance suite.
{
"events": [
{
"name": "invoice.paid",
"count": 42,
"lastSeenAt": 1799999999000,
"usedBy": [
"dunning"
]
},
{
"name": "trial.expiring",
"count": 0,
"lastSeenAt": null,
"usedBy": [
"trial-nudge"
]
}
]
} {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://api.mailkite.dev/v1/schemas/list-event-names-response.json",
"title": "List event names response body",
"type": "object",
"required": [
"events"
],
"properties": {
"events": {
"type": "array",
"description": "Most-recently-seen first, then names only a sequence declares (alphabetical — they have no recency to sort by).",
"items": {
"$ref": "event-name.json"
}
}
}
}