Event contract
Events are the trigger stream — the one way the outside world tells Mimeo that something happened. Everything the automation engine does starts here.
An event is a name, a person, and whatever details you want to carry. There is no schema and there never will be: your app's vocabulary is your data, not our product vocabulary.
POST /api/v1/events
Authorization: Bearer mm_your_token
Content-Type: application/json
{
"email": "ada@example.com",
"name": "signed_up",
"details": { "plan": "trial", "source": "ads" },
"occurred_at": "2026-07-29T10:00:00Z"
}
The fields
| Field | Notes |
|---|---|
email | Required. The universal key — lowercased, and the person is created if they're new. |
name | Required. Your own vocabulary. Triggers match on it exactly. |
details | Any flat object. Trigger matches and event_occurred conditions read these. |
occurred_at | Optional. Defaults to now. Timeline ordering. |
dedup_key | Optional. A repeat of the same key is recognised as a duplicate and does nothing, which makes an at-least-once sender safe. |
person | Optional. first_name, last_name, fields, tags — upserted alongside the event. |
attribution | Optional, first touch only: landing page, referrer, UTMs, device, country. |
Batching
Send up to 100 at once as { "events": [ … ] }. The response carries a
per-event result, so a partial failure tells you exactly which ones.
{ "results": [
{ "status": "created", "person_id": 41, "event_id": 902 },
{ "status": "duplicate", "person_id": 41, "event_id": 887 },
{ "status": "error", "error": "email is required" } ] }
Events Mimeo writes itself
Your events aren't the only ones in the stream. State changes fire events too, so a flow can trigger on them exactly as it would on yours:
| Event | When |
|---|---|
tag_added · tag_removed | A tag changes, by hand or by a flow. Details carry the tag. |
field_changed | A custom field changes. Details carry the key, the old value and the new one. |
unsubscribed · resubscribed | Suppression state changes. Details carry the reason and how it happened. |
sequence_completed | Someone reaches the end of a sequence. |
A flow's fire_event step writes one too — which is how one flow hands
off to another. The validator warns (self_trigger) if a flow fires
the event that triggers itself.
Money is events
Purchases, subscriptions and payments are recorded from events with the right names and details — Mimeo never integrates a payment processor directly, because your systems already know and one source of truth beats two. See People for the shapes.
What events are not
Opens and clicks are not events. They're telemetry, in their own
tables, with bot filtering — deliberately not part of the trigger stream, because
a flow branching on "opened" is a flow branching on a number that Apple Mail
Privacy Protection inflates. Use the opened_email and
clicked_email conditions if you want them, knowing that.
Tags don't decide. Tags describe. Anything a flow branches on should be real state — a field, a purchase, a subscription, an event that actually happened.
Designing your event names
- Name what happened, in the past tense, from the person's side:
signed_up,started_trial,viewed_pricing. - Prefer one event with details over many near-identical names.
plan_changedwith{ from, to }beatsupgraded_to_pro,upgraded_to_teamand the rest — a trigger match can narrow it, and you don't have to touch a flow when you add a plan. - Send a
dedup_keyif your sender might retry. It's the cheapest insurance there is.
See also: Events API · Flow schema