Event vocabularies
Sendly has two event name lists — one a webhook subscription accepts, one a workflow triggers on — and they are not the same
Sendly names events in two places, and the names are not the same in both. Nothing warns you about this: a name from the wrong list is either rejected as invalid or, worse, accepted as a custom event that will never fire.
- Webhook subscription events are what
createWebhookaccepts ineventTypes. A fixed list of ten. Anything else is a 400. - Workflow trigger events are what the system records on the timeline and what a workflow's event trigger matches on. A longer list, plus your own custom names.
Four names appear in both lists and mean the same thing in each: email.sent, email.opened,
email.clicked and contact.unsubscribed. Every other name belongs to one list only, and three
pairs are near-misses that differ by a letter or two.
Check which list you are in before hardcoding a name
The past tense is the tell, and it is not reliable. A subscription wants email.delivered; a
workflow triggers on email.delivery. A subscription wants email.bounced; a workflow triggers
on email.bounce.
Webhook subscription events
The ten values POST /api/webhooks accepts. A subscription names one or more of them.
| Event | When it fires |
|---|---|
email.sent | The message was handed to the sending infrastructure. |
email.delivered | The recipient's server accepted it. |
email.opened | The message was opened. |
email.clicked | A link in the message was clicked. |
email.bounced | The message hard-bounced. A soft bounce does not fire this. |
email.complained | The recipient marked it as spam. |
email.failed | The send failed before or at handoff — rejected, or unrenderable. |
contact.created | A contact was created, by any route including an import. |
contact.unsubscribed | A contact's status changed to unsubscribed. |
contacts.bulk_created | A bulk import finished creating contacts. |
Workflow trigger events
What the system records and what a workflow's event trigger matches. You do not subscribe to these; you build a workflow whose trigger names one.
Recorded by Sendly itself:
| Event | When it fires |
|---|---|
email.sent | The message was handed to the sending infrastructure. |
email.opened | An open was recorded — by Sendly's tracking pixel, or by the provider's own open tracking. |
email.clicked | A click was recorded — by Sendly's rewritten link, or by the provider's own click tracking. |
email.received | Inbound mail arrived on a verified domain. |
contact.subscribed | A contact's status changed to subscribed. |
contact.unsubscribed | A contact's status changed to unsubscribed. |
mailbox.message.received | A message arrived in a Sendly mailbox. |
mailbox.message.sent | A message was sent from a Sendly mailbox. |
Recorded from the sending infrastructure's own reports:
| Event | When it fires |
|---|---|
email.delivery | The recipient's server accepted the message. |
email.bounce | The message bounced, hard or soft. |
email.complaint | The recipient marked it as spam. |
email.reject | The message was refused before it was sent. |
email.renderingfailure | A template variable could not be rendered. |
email.deliverydelay | Delivery is being retried after a temporary failure. |
One open can be recorded twice
Two independent things detect an open: Sendly's tracking pixel and the provider's own open
tracking. They used to record it under two different names, so a workflow saw whichever half it
had named. Both now record email.opened, and clicks likewise record email.clicked — which
means one message can fire the trigger more than once, because both detectors can see the same
open. A workflow on these names should be written to tolerate that. If you are counting opens,
read the message's own opens field rather than counting events.
The two lists spell the same events differently
A webhook subscribes to email.delivered; a workflow triggers on email.delivery. Same for
email.bounced against email.bounce, and email.complained against email.complaint. The
names come from different places and neither list is going to silently change under a running
integration, so check the list you are actually using rather than assuming the other one's
spelling.
Names you make up
Two more kinds of trigger name exist, and neither is on a fixed list:
- Segment membership. A segment called "VIP Users" produces
segment.vip-users.entryandsegment.vip-users.exit. The slug is derived from the segment's name, so renaming the segment renames its events. - Your own events.
POST /api/v1/eventsrecords any name you give it, and a workflow can trigger on it. This is how an integration turns something that happened in another system into something Sendly can act on.
A workflow trigger on a name nothing ever records is not an error — it simply never fires. That is the failure mode this page exists to prevent.