Activation Emails: Trigger on Behavior, Not the Calendar
Time-based drip emails nag users who are already active and miss the ones who are stuck. How to define stuck states and send one useful email for each of them.

On this page(9 sections)
A calendar-based drip treats every new user the same: day one, day three, day seven, regardless of what they've done. The user who set everything up in ten minutes gets told how to get started, and the user who got stuck on step two gets a feature announcement.
Behavior-triggered emails flip that. Instead of asking "how many days since signup?", they ask "where is this person right now, and what would help them take the next step?"
Start by defining activation
You can't trigger on behavior until you've decided which behavior matters. Activation is the moment a new user first gets the value they came for. It's usually a single, observable event:
- An invoicing tool: the first invoice sent to a real customer.
- A monitoring tool: the first check running and reporting.
- A scheduling tool: the first booking made by someone else through the link.
- A team chat tool: the first conversation between two people.
Pick one event. If you're unsure which, look at accounts that are still active after a few months and ask what almost all of them did early on that inactive accounts didn't. You're looking for an early behavior that separates the two groups, not a vanity action like "viewed the dashboard".
Map the stuck states
Between signup and activation, users stall in predictable places. List them. For a hypothetical reporting tool, it might look like this:
- Signed up, never logged in again.
- Logged in, never connected a data source.
- Connected a source, but the sync failed.
- Data synced, no report created.
- Report created, never shared or scheduled.
Each of these is a different problem. Someone stuck at state 3 needs troubleshooting help; someone at state 5 needs a reason to share. One generic "need help getting started?" email can't serve both.
One email per stuck state
Now write exactly one email for each state. Each email names the situation, explains why it matters in one sentence, and links straight to the fix.
| Stuck state | Trigger | Link goes to | |
|---|---|---|---|
| Never returned | No session 24h after signup | "Your workspace is waiting" with one setup step | The first setup screen |
| No data source | Logged in, no source after 48h | "Connect a source in two minutes" plus a sample-data option | Source picker |
| Sync failed | Sync error event | "Your sync hit a problem. Here's the fix" with the actual error | Source settings |
| No report | Synced, no report after 72h | "Build your first report from a template" | Template gallery |
| Not shared | Report exists, not shared after 5 days | "Send this report to your team every Monday" | Schedule dialog |
Notice the sync-failure row. That email isn't on a delay at all; it fires on an error event. Some of the most useful activation emails are reactions to something going wrong, sent while the user still remembers trying.
Name your events like a contract
Behavior-triggered email depends on reliable product events. A few conventions save pain later:
- Past tense, object first:
source.connected,sync.failed,report.created,report.shared. - Include the account and user: every event should carry both, because B2B activation often happens at the account level even when one user does the work.
- Record the timestamp at the source, not when the email system processes it.
- Version the payload if you change what an event means.
Write the list down in one place. Product, engineering and whoever writes the emails should all point at the same definitions.
Evaluate at send time
The biggest mistake with behavior-based emails is deciding too early. If you schedule "no report after 72 hours" at signup, then send it on day three without checking again, the user who created a report on day two still gets nagged.
Always re-check the condition immediately before sending:
async function maybeSendNoReportEmail(accountId: string) {
const account = await getAccount(accountId);
if (account.reportsCount > 0) return; // already done
if (await alreadySent(accountId, "no_report")) return;
if (await sentAnyEmailWithin(accountId, hours(24))) return; // frequency cap
await sendEmail(account.ownerEmail, "no_report", { templateUrl: account.templatesUrl });
await markSent(accountId, "no_report");
}
The job that calls this can run hourly. Whatever it uses to send, whether a transactional API or SMTP relay, the decision logic lives in your code, close to the data.
Guardrails that keep it from becoming spam
Behavior triggers can misfire in bursts, especially when several conditions become true at once. Add these limits from the start:
- A frequency cap. No more than one onboarding email per user per day, and perhaps three per week.
- A priority order. If two emails qualify at once, send the one for the earliest stuck state and drop the other.
- A hard stop at activation. Once the activation event fires, the onboarding track ends. Other lifecycle emails can take over.
- A sunset. If someone hasn't responded to anything after a few weeks, stop. Silence is an answer.
- A human override. If a support thread or sales conversation is open, pause automated nudges for that account.
What about the calendar entirely?
Time still matters; it just isn't the trigger on its own. "No data source" is a state, but you only email about it after giving the user reasonable time to get there without help. In practice, most good activation emails combine a state with a delay: "in state X for at least Y hours."
A few calendar-only emails are fine too: a trial ending notice has to go on a date, and a weekly summary is weekly by design. The point is that nudges about progress should be driven by progress.
How to tell if it's working
Track, per email:
- How many users entered the stuck state.
- How many received the email.
- How many left the stuck state within, say, 72 hours of receiving it.
- The same exit rate for a small holdout group that didn't get the email.
The difference between the last two numbers is what the email actually did. If it's zero, rewrite or remove it.
Key takeaways
- Define activation as one observable event that separates retained accounts from the rest.
- List the stuck states between signup and activation, and write one email per state.
- Fire on events, including failures, and always re-check the condition right before sending.
- Cap frequency, prioritize, stop at activation, and pause when a human is already talking to the account.
- Judge each email by how many people it moves out of a stuck state compared with a holdout.
Start with Koltrix
Your domain, one inbox, and an API that sends.
A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.


