Skip to content

Event triggers

Fire a template automatically when a reported event matches — with rate limits, quiet hours, dedup, and dynamic per-recipient attachments.

An event trigger is the only “event happened, send an email” mechanism in Retidal. There is no general-purpose “send this one email to this one address” API — for a one-off send to a specific person, use a campaign whose audience filter matches exactly one user.

Authentication for every endpoint on this page is the console session cookie — see Providers § Authentication. Writes need EDITOR+.

The event pipeline needs identify, not just track

A trigger matches on the identified user behind an event. If your application only calls the event-ingestion endpoint and never calls POST /api/v1/identify with that user’s email trait, the trigger log will show every match skipped as no_identified_user (never identified) or no_email (identified, but no email trait) — and you will get no error anywhere else. Identify the user (with traits.email set) before the event you want to trigger on, not after.

POST https://retidal.com/api/projects/{projectId}/email/triggers
Request body
{
"name": "Welcome on sign-up",
"eventName": "sign_up",
"match": {
"properties": { "plan": "pro" },
"userFilter": { "hasEmail": true, "locale": "zh-CN" }
},
"templateId": "tpl_x",
"providerId": "prov_x",
"category": "transactional",
"enabled": true,
"maxSendsPerWindow": 1,
"windowSeconds": 86400,
"quietHoursStartHour": 22,
"quietHoursEndHour": 8,
"quietHoursMode": "defer",
"dedupeWindowSeconds": 3600
}
namestringrequired

1–120 characters.

eventNamestringrequired

1–200 characters. Must match the eventName your application reports to POST /api/v1/t exactly.

templateIdstringrequired

Must belong to the same project.

providerIdstring

Channel to send from. Nullable — omit to let routing pick one by category.

identityIdstring

A specific sending identity to send as, nullable.

categorystring

transactional | marketing. Default transactional. Marketing triggers are blocked until compliance settings are configured.

enabledboolean

Default true.

matchobject

See below. additionalProperties is disallowed — an unrecognized key is a 400, not a silent no-op.

match has exactly three possible keys:

Key Purpose
properties Exact-match against the triggering event’s own properties object.
conditionTree A nested (2-level) any/all fact-rule tree for conditions properties alone can’t express. Nullable.
userFilter Audience/segment filter — same shape as a campaign’s audienceFilter (hasEmail, locale, paidStatus, valueTier, queueKey, and more — see Campaigns § audienceFilter for the full field list).
Field Range Default Purpose
maxSendsPerWindow 1–100,000, nullable unset Cap sends to the same user within windowSeconds.
windowSeconds 1–31,536,000 86400 Window the send cap above applies to.
quietHoursStartHour / quietHoursEndHour 0–23, nullable unset Hour range (project-local) to hold sends. Set both together or neither.
quietHoursMode defer drop defer
dedupeWindowSeconds 0–31,536,000 3600 Suppresses a second send for the same (user, trigger) pair inside this window.

Guardrails are evaluated in this order — suppression list → rate cap → quiet hours → dedup — and the trigger log’s skipReason names whichever one stopped the send.

json
"dynamicAttachments": [
{ "fromProperty": "invoice_url", "filename": "invoice.pdf" }
]
fromPropertystringrequired

1–120 characters. The property name on the triggering event that holds a URL — read at send time, not stored ahead of time.

filenamestring

1–200 characters. Falls back to a name inferred from the URL if omitted.

Up to 3 dynamicAttachments entries per trigger. This is how “send an invoice after payment” works end to end: your application includes an invoice_url property on the payment_succeeded event it reports, the trigger is configured with dynamicAttachments: [{ fromProperty: "invoice_url" }], and — critically — that URL’s domain has to already be on the dynamic-attachment allowlist, or the fetch fails closed and the attachment silently doesn’t go out (the email itself still sends).

If fromProperty is missing from the event, or isn’t a string, that one attachment is marked attachment_fetch_failed on the sent message — the rest of the email still sends.

GET /api/projects/{projectId}/email/triggers/{id}/log

Returns the latest 50 evaluation records with status/skipReason. Work through these in order:

  1. No log entries at all — the eventName doesn’t match what’s actually being reported, or the trigger is enabled: false, or the event was never reported (check with the console analytics/events query).
  2. no_identified_user — the event’s userId was never passed to POST /api/v1/identify.
  3. no_email — identified, but the traits.email field was never set.
  4. no_matchproperties/conditionTree/userFilter didn’t match this event or this user.
  5. suppressed — the recipient is on the suppression list for a reason that blocks this category.
  6. capped — hit maxSendsPerWindow.
  7. quiet — inside the quiet-hours window (deferred or dropped per quietHoursMode).
  8. deduped — inside dedupeWindowSeconds of a previous send to the same user.

If none of those explain it, check the channel itself: GET /api/projects/{projectId}/email/providers to confirm the providerId isn’t a log-driver channel (which “sends” but delivers nothing), and GET /api/projects/{projectId}/email/messages?limit=20 for the actual send status of any message that did get created.