How to Send Emails Based on Product Events (API Guide)

The most effective SaaS emails are triggered by what users actually do in your product. User signs up but doesn't complete onboarding? Send a help email. User creates their first project? Send tips for getting more value. User hits a usage limit? Send an upgrade prompt.
This is event-based email, and it requires connecting your product's event stream to your email platform. This guide walks through the technical implementation: how to track events, send them to your email system, and build automations that respond to user behavior.
Why Event-Based Emails Outperform Batch Campaigns
Before diving into the technical details, it's worth understanding why event-based emails are so much more effective than traditional batch campaigns.
Batch campaigns (like newsletters, promotional blasts, or scheduled sequences) are sent at a fixed time to a group of users regardless of what those users are doing. They're useful for announcements and general content, but they can't respond to individual behavior. A batch email about your reporting feature hits the same whether the user just discovered reporting yesterday or has been using it daily for six months.
Event-based emails are triggered by specific user actions, which means they arrive at exactly the right moment with exactly the right content. A user who just connected their first integration receives tips about getting the most from integrations — right when they're thinking about integrations. This timeliness and relevance is why event-based emails consistently see 2-3x higher open rates and 3-5x higher click rates compared to batch campaigns.
The key difference is context. Batch emails guess at what users might find relevant. Event-based emails know what users just did and respond accordingly. This is the foundation of behavioral email marketing for SaaS, and it applies to every stage of the user lifecycle: onboarding, engagement, conversion, retention, and win-back.
The Event-Based Email Architecture
Before diving into code, understand how the pieces fit together.
Your application generates events when users take actions. These events include information about what happened, who did it, and any relevant metadata. For example: "user_created_project" by user 123, with project name "Q1 Marketing Campaign."
These events need to get to your email platform. You can send them directly via API, or through an event streaming service like Segment. Either way, the email platform receives a stream of events with user identifiers and event properties.
Your email platform uses these events as triggers for automations. You configure rules like "when user_created_project fires for the first time, send the project tips email." The platform matches incoming events to rules and sends the appropriate emails.
The user receives an email that feels timely and relevant because it's responding to something they just did.
Here's the flow visually:
User Action → Your App → Event API Call → Email Platform → Automation Check → Email Sent
Defining Your Events
Start by defining the events you want to track. Don't try to track everything. Focus on events that map to meaningful moments in the user journey.
For most SaaS products, you need events in these categories:
Account events: user_signed_up, user_verified_email, user_completed_onboarding, user_invited_teammate
Feature events: The first time a user uses each major feature. project_created, integration_connected, report_generated, automation_activated
Engagement events: user_logged_in, user_returned_after_inactivity (if a user logs in after 7+ days of inactivity)
Billing events: subscription_started, subscription_upgraded, subscription_cancelled, payment_failed
Each event should include the user identifier (email or user ID) and relevant properties. For project_created, you might include project_name and project_type. For subscription_upgraded, you might include old_plan and new_plan.
Keep event names consistent. Use snake_case or camelCase throughout, not a mix. Use past tense (user_created_project) or present tense (user.created_project) consistently. Your future self will thank you.
Designing an Event Naming Convention
A consistent naming convention prevents confusion as your event library grows. Here's a proven approach:
Format: {object}_{action} in snake_case
Examples:
project_created(notcreated_projectorProjectCreated)integration_connectedreport_exportedsubscription_upgraded
Rules:
- Use past tense for completed actions:
project_created, notproject_create - Include the object being acted on:
report_exported, not justexported - Be specific:
team_member_invitedis better thanuser_updated - Avoid generic events:
button_clickedis almost never useful for email triggers
Properties should be typed and documented. Create a schema for each event:
// Event: project_created
{
event: 'project_created',
email: string, // Required: user's email
properties: {
project_id: string, // Required: unique project identifier
project_name: string, // Required: display name
project_type: 'personal' | 'team', // Required: project category
is_first_project: boolean, // Required: used for first-time triggers
template_used: string | null // Optional: which template they started from
},
timestamp: string // ISO 8601
}Document every event in a shared location (internal wiki, README, Notion database) so your engineering team, marketing team, and email platform all agree on what events exist and what they mean.
Sending Events to Your Email Platform
Most email platforms accept events through a REST API. The typical pattern is a POST request with the event name, user identifier, and properties.
Here's what the API call generally looks like:
// Example: sending an event when a user creates a project
await fetch('https://api.emailplatform.com/v1/events', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
event: 'project_created',
email: user.email,
properties: {
project_id: project.id,
project_name: project.name,
is_first_project: isFirstProject
},
timestamp: new Date().toISOString()
})
})Call this API from your application code whenever the relevant action happens. In this example, you'd call it right after successfully creating a project in your database.
Some platforms also accept events through client-side JavaScript. This is useful for tracking frontend actions like button clicks or page views. But for important events like project creation, server-side tracking is more reliable because it's not affected by ad blockers or browser issues.
If you're using an event streaming service like Segment, you send events to Segment, and Segment forwards them to your email platform (and any other tools you've connected). This decouples your application code from specific tool integrations.
Creating a Reusable Event Tracking Module
Don't scatter API calls throughout your codebase. Create a centralized module that handles event tracking:
// lib/events.ts
class EventTracker {
private apiKey: string;
private baseUrl: string;
constructor(apiKey: string, baseUrl: string) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
async track(email: string, event: string, properties: Record<string, unknown> = {}) {
try {
const response = await fetch(`${this.baseUrl}/v1/events`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event,
email,
properties,
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
console.error(`Event tracking failed: ${event}`, await response.text());
}
} catch (error) {
// Log but don't throw - event tracking should never break the main flow
console.error(`Event tracking error: ${event}`, error);
}
}
}
export const events = new EventTracker(
process.env.EMAIL_PLATFORM_API_KEY!,
process.env.EMAIL_PLATFORM_URL!
);Then use it consistently throughout your application:
// In your project creation handler
const project = await db.insert(projects).values({ ... }).returning();
// Non-blocking event tracking
events.track(user.email, 'project_created', {
project_id: project.id,
project_name: project.name,
is_first_project: userProjectCount === 0
});This pattern ensures consistent event formatting, centralized error handling, and easy testing (you can mock the EventTracker in tests).
Handling Events Reliably
Event delivery needs to be reliable. If an event doesn't reach your email platform, the user doesn't get the email they should receive.
Make event sending asynchronous. Don't block your main application flow waiting for the email platform to respond. Send the event in a background job or fire-and-forget, so a slow or failed API call doesn't affect user experience.
Implement retry logic. If the email platform's API returns an error or times out, retry the request. Most platforms have rate limits, so implement exponential backoff: wait 1 second before the first retry, then 2 seconds, then 4, etc.
Log events for debugging. When something goes wrong with email delivery, you'll want to check whether the event was actually sent. Log the event name, user, and timestamp whenever you send an event.
Handle edge cases. What happens if the user doesn't have an email address yet (anonymous users)? What happens if the event fires multiple times due to retries? Design your event sending to handle these gracefully.
Idempotency
Some events might fire more than once due to retries, race conditions, or application bugs. Your email automations need to handle this gracefully.
Include a unique event ID with each event so your email platform can deduplicate:
{
event: 'project_created',
email: user.email,
event_id: `project_created_${project.id}_${Date.now()}`, // Unique identifier
properties: { ... }
}On the automation side, use "send only once" settings for milestone emails. The combination of unique event IDs and single-send rules prevents users from receiving duplicate emails even if the underlying event fires multiple times.
Building Automations from Events
Once events are flowing to your email platform, build automations that respond to them.
The basic automation structure is: trigger + conditions + action.
The trigger is the event. "When project_created fires." Some platforms let you add conditions to the trigger itself, like "when project_created fires and is_first_project is true."
Conditions filter which users receive the email. You might add conditions like "user is on free plan" or "user signed up less than 30 days ago." Conditions let you target the right users without creating dozens of separate events.
The action is sending the email. You select which email template to send, and the platform sends it to the user who triggered the event.
Here's an example automation:
Trigger: project_created Conditions: is_first_project = true, user.plan = free Action: Send "First Project Tips" email
This sends the first project email only to free users, and only on their first project. Paid users or users creating subsequent projects don't receive it.
Multi-Step Automations
Simple trigger-and-send automations are powerful, but multi-step flows handle complex scenarios:
Onboarding flow with conditional branching:
Trigger: user_signed_up
→ Send welcome email
→ Wait 24 hours
→ Check: Has user completed onboarding?
→ Yes: Send "Great progress" email with next steps
→ No: Send "Need help getting started?" email
→ Wait 48 hours
→ Check: Has user created first project?
→ Yes: Send "First project tips" email
→ No: Send "Here's what you're missing" email
This kind of branching logic ensures every user gets the right message at the right time. For a complete walkthrough of building these sequences, see our guide on creating an onboarding email sequence.
Timing and Delays
Not every event-triggered email should send immediately. Sometimes a delay improves the experience.
Consider a "signup without activation" email. You don't want to send this immediately after signup. The user is probably still exploring. Instead, trigger it 24 hours after signup, with a condition that the user hasn't activated yet.
Most email platforms support delays in automations. The structure becomes: trigger + delay + condition check + action.
Trigger: user_signed_up Delay: 24 hours Condition: user_completed_onboarding = false Action: Send "Need help getting started?" email
The condition is checked after the delay, not at the time of the trigger. This is important. The user might complete onboarding during the 24-hour delay, in which case they shouldn't receive the email.
For immediate emails (like "thanks for upgrading"), skip the delay. For nudge emails (like "you haven't finished setup"), add appropriate delays.
Choosing the Right Delay
Here are guidelines for common scenarios:
| Scenario | Recommended Delay | Reason |
|---|---|---|
| Welcome email | Immediate | User expects it |
| Activation nudge | 24 hours | Give them time to explore first |
| Feature tips | 1-2 hours | They're actively using the product |
| Inactivity alert | 7-14 days | Need enough silence to be meaningful |
| Upgrade prompt | 1-2 hours | Strike while they're hitting limits |
| Usage milestone | Immediate | Celebrate the moment |
| Re-engagement | 7+ days | Don't rush the check-in |
Handling First-Time vs. Repeat Events
Many automations should only fire the first time an event happens. You don't want to send "congrats on your first project" every time someone creates a project.
There are two approaches to handling this.
The first approach is to include "is_first" information in the event itself. When you send project_created, include is_first_project: true or false based on your database. Your automation conditions check this property.
The second approach is to let the email platform track whether a user has received a specific email. Most platforms have a "send only once" option for automations. The platform remembers that user 123 already received the first project email and won't send it again, even if the event fires multiple times.
The first approach gives you more control and makes debugging easier. The second approach is simpler to implement but can be harder to troubleshoot.
Common Event-Triggered Automations
Here are the automations most SaaS products should have:
Welcome email: Trigger on user_signed_up. Send immediately. No conditions needed; everyone who signs up should receive it.
Activation nudge: Trigger on user_signed_up. Delay 24 hours. Condition: user has not completed activation. Send only once.
First feature milestone: Trigger on each major feature event (first project, first integration, etc.). Send immediately. Condition: is_first_time = true.
Inactivity re-engagement: This is trickier because it's based on the absence of events. Most platforms let you trigger on "user has not done X in Y days." Trigger when user hasn't logged in for 7 days. Send re-engagement email.
Upgrade prompt: Trigger on usage_limit_approached (you'd fire this event when usage hits 80% of limit). Condition: user is on free or starter plan. Send upgrade information.
Trial expiration: Trigger on trial_ending_soon (fire this event 7 days before trial ends). Begin the trial expiration sequence.
Payment failure: Trigger on payment_failed. Send dunning email immediately. This is critical for reducing involuntary churn.
Account verification: Trigger on user_signed_up. Send verification email immediately. Required before the user can take any other actions.
Advanced Automations
Once you have the basics working, consider these more sophisticated triggers:
Feature adoption chain: When a user completes one key feature, suggest the next. project_created → suggest integrations. integration_connected → suggest team invites. team_member_joined → suggest shared workflows. This creates a guided path through your product.
Usage milestone celebration: When users hit meaningful milestones (100th task completed, 50th report generated, 1 year anniversary), send a congratulatory email. These build emotional connection and reinforce the value they're getting.
Churn risk signal: Combine multiple signals — usage drop + support ticket + pricing page visit — to trigger a proactive outreach email before the user decides to cancel. This requires tracking multiple events and using compound conditions.
The Role of Events in Email Sequences
Events don't just trigger individual emails — they power entire email sequences. An automated sequence is a series of emails sent over time, where events determine both when the sequence starts and how it adapts to user behavior.
For example, a trial conversion sequence might work like this:
- Entry trigger: user_signed_up (with trial = true)
- Email 1: Welcome + first action guide (immediate)
- Branch: Check if user_completed_onboarding within 24 hours
- Yes: Send "Power user tips" email
- No: Send "Need help?" email
- Email 3: Feature spotlight (day 5)
- Branch: Check user activity level
- Active: Send social proof + upgrade nudge
- Inactive: Send re-engagement
- Email 5: Trial ending notification (3 days before expiration)
- Email 6: Last day urgency (1 day before expiration)
Every step in this sequence is informed by events. The sequence adapts to what each user actually does rather than following a rigid timeline. For more on building effective conversion sequences, see our guide on converting free trial users with email.
Debugging Event-Triggered Emails
When an email doesn't send as expected, debugging can be tricky. Here's how to approach it.
First, verify the event was sent. Check your application logs. Did the API call succeed? Did it include the right user and properties?
Second, verify the event was received. Most email platforms have an event log or debugger. Check if the event appears there with the correct data.
Third, verify the automation matched. Check the automation's activity log. Did the event match the trigger? Did the user meet the conditions? Was there a suppression rule that prevented sending?
Fourth, check delivery. If the automation fired but the user didn't receive the email, check deliverability. Did the email bounce? Is the address valid? Is it in spam? Our email deliverability guide covers the full set of things that can go wrong.
Build visibility into your event pipeline. The more you can see what's happening at each stage, the faster you can fix issues.
Common Debugging Scenarios
"The email never sent." Check: Was the event sent? Was it received? Did the automation match? Was the user suppressed (unsubscribed, already received, frequency cap)?
"The email sent twice." Check: Was the event fired twice? Is the automation set to "send only once"? Was there a retry that succeeded both times?
"The email sent to the wrong person." Check: Was the correct email address included in the event? Was the user identified correctly in your email platform?
"The email content was wrong." Check: Were the event properties correct? Is the template using the right dynamic fields? Is there a caching issue with template rendering?
Testing Event-Triggered Emails
Test your automations before going live. Create test user accounts and manually trigger events to verify emails send correctly.
Most email platforms have a test mode that lets you trigger automations without sending real emails. Use this during development.
Test the full flow: trigger the event, verify the automation fires, verify the email renders correctly with real user data, verify personalization works, verify links work.
Test edge cases: What happens if a required property is missing? What happens if the user's email is invalid? What happens if the event fires twice? Make sure your automations handle these gracefully.
Building a Testing Framework
Create a simple script or internal tool that lets you fire test events:
// scripts/test-events.ts
import { events } from '../lib/events';
const testEmail = 'test@yourcompany.com';
async function testOnboardingFlow() {
// Step 1: Simulate signup
await events.track(testEmail, 'user_signed_up', {
plan: 'free_trial',
source: 'test_script'
});
console.log('Sent: user_signed_up');
// Step 2: Wait and simulate activation
await new Promise(resolve => setTimeout(resolve, 5000));
await events.track(testEmail, 'project_created', {
project_id: 'test-123',
project_name: 'Test Project',
is_first_project: true
});
console.log('Sent: project_created');
// Step 3: Simulate team invite
await events.track(testEmail, 'team_member_invited', {
invited_email: 'colleague@test.com',
is_first_invite: true
});
console.log('Sent: team_member_invited');
}
testOnboardingFlow();Run this script against a staging environment with test accounts. Verify each email in the sequence fires correctly, with the right content and timing.
Scaling Considerations
As your user base grows, event volume increases. A few things to consider:
Batch events if possible. If your email platform supports it, batch multiple events into a single API call. This reduces network overhead and helps you stay within rate limits.
Use queues for reliability. Instead of sending events directly from your application, push them to a message queue (like Redis, RabbitMQ, or SQS) and process them with a background worker. This decouples event generation from event delivery and makes the system more resilient.
Monitor event lag. If events are queuing up and not being processed quickly, your emails will be delayed. Set up monitoring to alert you if event processing falls behind.
Consider event volume in your email platform pricing. Most platforms charge based on contacts, emails sent, or events processed. High event volume can significantly impact costs. Only send events that you actually use for email triggers or segmentation.
Performance Benchmarks
At different scales, different approaches make sense:
< 1,000 events/day: Direct API calls from your application are fine. No queue needed. Event tracking can be synchronous if your platform API is fast.
1,000-10,000 events/day: Use fire-and-forget async calls. Add basic retry logic. Start monitoring delivery rates.
10,000-100,000 events/day: Implement a message queue. Use batch API endpoints where available. Monitor event lag closely. Consider dedicated worker processes for event delivery.
100,000+ events/day: Evaluate whether all events are necessary. Implement event filtering before sending. Use a dedicated event streaming platform (Segment, RudderStack). Monitor costs carefully.
Security Considerations
Event data often contains user information and product usage details. Handle it responsibly.
Don't include sensitive data in events. Event properties should include identifiers and context, not passwords, payment details, or personally identifiable information beyond what's needed for email personalization.
Secure your API keys. The API key used to send events should be server-side only (never in client-side code). Rotate keys regularly and restrict permissions to only what's needed.
Validate before sending. Sanitize event properties before sending them to your email platform. A user-supplied project name that contains malicious HTML could end up in an email template if not properly escaped.
Respect user preferences. Events should check whether a user has opted out of emails before triggering automations. Some platforms handle this automatically, but verify that unsubscribed users don't receive event-triggered emails.
Getting Started
Here's a practical path to implementing event-based emails:
Week 1: Identify the 5 most important events for your product. User signup, activation, and 2-3 feature milestones. Design your event schema with names and properties.
Week 2: Instrument your application to send these events to your email platform. Start with server-side tracking for reliability. Create a centralized event tracking module.
Week 3: Build your first automation. Welcome email triggered by signup is the easiest starting point. Then add an activation nudge with a 24-hour delay.
Week 4: Add activation nudge and first feature milestone emails. Test thoroughly with test accounts. Verify the full flow from event to email delivery.
Week 5: Monitor performance and iterate. Check which emails are being opened and clicked. Refine your triggers and content. Start planning more advanced automations.
Event-based email is a significant improvement over batch campaigns, but it requires upfront investment in instrumentation. Start simple, get the infrastructure working, then expand to more sophisticated automations. The result is emails that feel timely and relevant because they genuinely are. For a broader look at what you can build with this foundation, see our guide on automated email sequences and our overview of email sequence templates.
Frequently Asked Questions
What's the minimum number of events I need to start?
Three to five events are enough to build a meaningful automation system: user_signed_up, user_completed_onboarding, first_key_feature_used, and optionally subscription_upgraded and payment_failed. You can always add more events later as your needs evolve.
Should I use server-side or client-side event tracking?
Server-side for any event that triggers an email. Client-side tracking is unreliable — ad blockers, browser extensions, and network issues can prevent events from firing. Use client-side tracking only for supplementary analytics (page views, UI interactions) that don't trigger critical emails.
How do I handle events for users who haven't verified their email?
Queue the events but don't trigger email automations until the email is verified. Store the events so that once verification completes, you can process them and the user enters the appropriate automation flows. Never send marketing or onboarding emails to unverified addresses.
What happens if my email platform's API is down?
Implement a retry queue. Store failed events locally (in a database or message queue) and retry them with exponential backoff. Most API outages are brief, so events will eventually be delivered. Set up monitoring to alert you if the queue grows beyond a threshold.
Can I use event data for email personalization?
Yes, and you should. Event properties can be used as dynamic variables in email templates. "You just created [project_name] — here's how to get the most from it" is more engaging than a generic "Here are some tips." Make sure your templates gracefully handle missing properties with fallback values.
How do I prevent email fatigue from too many event triggers?
Implement frequency caps — don't send more than one event-triggered email per user per day (or per 12 hours for high-touch products). Prioritize by importance: a payment failure email takes precedence over a feature tip. Most email platforms support frequency capping natively.
Should I track every user action as an event?
No. Only track events that serve a specific purpose: triggering an email, updating a user profile, or powering segmentation. Tracking everything creates noise, increases costs, and makes your event system harder to maintain. Be intentional about what you track.