Search for "which events to track" and you get product analytics tracking plans: every click, every page, every feature, piped into a dashboard. That's a fine plan for answering questions about usage. It is the wrong plan for email.
Email needs a much shorter list. An event belongs on it only if it changes what someone receives. Everything else can live in your analytics tool, where nobody gets an email because of it.
This post gives you that shorter list: ten events that cover most of what a small SaaS product emails about, the naming rule that keeps them readable, the properties worth sending with each, and a spec you can paste into your repo. It picks up where event-triggered email leaves off. That post covers why email should react to what users do and how an event reaches a sequence. This one covers which events to send.
The test for an email event
Before you add an event, name the email it drives. There are three jobs an event can do, and it should do at least one:
- Start something. It enrolls the user in a sequence or sends a transactional email.
user.signed_upstarts the welcome sequence. - Stop something. It counts as a goal, so a sequence stops nudging someone who already did the thing.
subscription.startedends the trial sequence. - Fill something in. Its properties supply the details an email quotes: the plan name, the amount, the project the user just created.
If you can't name the email, leave the event out. You can always add it later, and an event nobody listens to is only noise in your logs.
How to name them
Use object.past_tense, lowercase, with dots between the parts:
user.signed_up
project.created
subscription.cancelled
A few rules keep the list readable a year from now:
- Past tense. An event records something that already happened.
payment.failed, notpayment.failorfail_payment. - The object first. Related events sort together:
subscription.started,subscription.upgraded,subscription.cancelled. - Your product's nouns. If your users make "boards", fire
board.created, notitem.created. The name should read like a sentence about your product. - No values in the name.
plan.upgraded_to_probecomes one event per plan. Sendsubscription.upgradedwith the plan as a property instead. - Pick once. The name is the only link between your backend and the sequence listening for it. Renaming it later means changing both.
In Pombo, event names match regardless of case, but pick one style and keep to it anyway. The people reading your code will thank you.
The starter list
Ten events, grouped by the stage of the customer's life they belong to. You won't need all of them on day one. The last section covers where to start.
Getting started
1. user.signed_up Fire it when the account is created and saved. It starts your welcome sequence and it's the event almost every other flow builds on. Properties: first_name, signup_source, plan.
2. onboarding.completed Fire it when a user finishes whatever setup your product needs before it's useful: connected a data source, installed the snippet, imported their list. It makes a good goal: set it on the welcome sequence with stopping turned on, and anyone who finishes setup stops getting the "here's how to set up" emails. Properties: steps_completed, days_since_signup.
3. The first real result Name this one after your product: report.created, invoice.sent, site.published. It's the moment a user first gets what they signed up for. Fire it on the first occurrence only, and use it to send a short "nice, here's what to try next" email while the moment is still fresh. Properties: the name or title of the thing, and a link to it.
Growing
4. teammate.invited In a product where teams matter, an invitation is a strong sign a trial will convert. It can send the inviter a note about roles and permissions, and it's a useful goal for a sequence that encourages collaboration. Properties: inviter_name, team_name.
5. feature.first_used Fire it the first time a user touches a feature you know predicts retention. Send the feature's name as a property rather than creating one event per feature. It works as a goal for a feature adoption sequence: stop explaining a feature once someone has used it. Properties: feature.
Paying
6. trial.started If signup and trial start happen at different times (a user picks a plan later, or a trial needs a card), track the trial separately. It starts the trial sequence. Properties: plan, trial_ends_on as a readable date.
7. subscription.started The conversion. It's the goal for your trial sequence, and it can start a short "welcome to the paid plan" sequence of its own. Properties: plan, amount, billing_interval.
8. payment.failed A card was declined on renewal. The notice that the payment failed is transactional and must always send. The reminders that follow can be a sequence, with payment.succeeded as its goal so they stop the moment the card works. Properties: amount, plan, update_payment_url.
Leaving
9. subscription.cancelled Fire it when the user cancels, not when access ends. It can send a short "sorry to see you go" email that asks one question, and start a win-back sequence that waits a few weeks before its first email. Properties: plan, cancellation_reason, access_ends_on.
10. user.went_inactive Your product can't notice someone doing nothing at the moment they do it, so this one comes from a scheduled job: once a day, find users with no activity in the last 14 days and post the event for each. It starts a re-engagement sequence, and an event that shows they came back can be its goal. Properties: days_inactive, last_active_on.
What to put in the properties
Properties are the details an email quotes. In Pombo they're available in the email as {{ event.plan }}, {{ event.amount }} and so on, with one rule that catches people out: send them ready to display.
{
"event": "payment.failed",
"email": "[email protected]",
"properties": {
"plan": "Growth",
"amount": "$49",
"update_payment_url": "https://app.example.com/billing"
}
}
Send "$49", not 4900 cents. Send "October 14", not a Unix timestamp. The value goes into the sentence exactly as you sent it.
Keep properties to what an email would actually say. Don't send passwords, tokens, full card numbers or anything you wouldn't want printed in an inbox. For properties that are only sometimes present, see the note on conditionals in event-triggered email.
Events are moments, fields are state
One distinction saves a lot of confusion later. An event records that something happened. A contact field records how things stand now.
When someone upgrades, two things are true: the upgrade happened (an event) and they're now on the Growth plan (a fact about them). Send both. Post subscription.upgraded to the events API, and update the contact's plan custom field through the contacts API.
It matters because they do different jobs. Events start sequences and count as goals. Fields are what you segment on ("everyone on the Growth plan") and what a date-based trigger reads.
That last point changes how you handle trial expiry. You might expect a trial.expiring event, but you don't need one: store trial_ends_on as a date field on the contact, and a sequence with a date field trigger can fire three days before it, with no scheduled job on your side.
Where to fire them in your code
Three rules keep the events honest:
- Fire after the change is saved. Post
subscription.startedafter the subscription is written to your database, not in the controller before the transaction commits. Otherwise a rolled-back payment can still send a "welcome to Pro" email. - Fire from a background job. Your email platform being slow or down should never stop a user from signing up. Queue the call and let it retry.
- Guard the once-only events. A retried request can post twice. For events that should happen once per user, such as the first real result, record that you sent it and check before sending again.
If you'd rather not write this by hand, a coding agent connected to your email platform can find the right places in your code and add the calls for you.
A spec you can copy
Paste this into your repo, rename the product-specific events, and delete the ones you don't need yet. It's the list above in a form you can hand to a teammate or an agent.
# Email events: each one starts, stops or fills in an email.
# Name: object.past_tense, lowercase. Values in properties are display-ready.
user.signed_up:
when: account created and saved
drives: starts the welcome sequence
properties: [first_name, signup_source, plan]
onboarding.completed:
when: user finishes required setup
drives: goal for the welcome sequence
properties: [steps_completed, days_since_signup]
report.created: # rename to your product's first real result
when: first time only
drives: sends a "what to try next" email
properties: [title, url]
teammate.invited:
when: invitation sent
drives: goal for a collaboration sequence
properties: [inviter_name, team_name]
feature.first_used:
when: first use of a feature that predicts retention
drives: goal for a feature adoption sequence
properties: [feature]
trial.started:
when: trial begins, if separate from signup
drives: starts the trial sequence
properties: [plan, trial_ends_on]
subscription.started:
when: first successful payment
drives: goal for the trial sequence
properties: [plan, amount, billing_interval]
payment.failed:
when: renewal charge declined
drives: transactional notice, plus a reminder sequence
properties: [amount, plan, update_payment_url]
payment.succeeded:
when: charge succeeds
drives: goal for the payment reminder sequence
properties: [amount, plan]
subscription.cancelled:
when: user cancels (not when access ends)
drives: starts the win-back sequence
properties: [plan, cancellation_reason, access_ends_on]
user.went_inactive:
when: daily job, no activity in 14 days
drives: starts the re-engagement sequence
properties: [days_inactive, last_active_on]
# State, not events: keep these on the contact as fields.
contact_fields:
plan: current plan name
trial_ends_on: date; drives the "trial ending" sequence by date trigger
(payment.succeeded is on the spec but not the list of ten: it's only there to be the goal that stops the payment reminders.)
Start with three
You don't need all of this before your first email goes out. Start with the three events that cover the most ground:
user.signed_up, to start a welcome sequence.onboarding.completed, so that sequence stops once it has done its job.subscription.started, so your trial emails stop at the conversion.
Add the next event from the list when you have an email for it, not before.
For the full picture of how events fit with sequences, segments and transactional email, see the lifecycle email basics section, or read the events docs for the API itself.
