Growth Engineering
Most SaaS onboarding email sequences fail because they're either too generic or too aggressive. Here's the structure that works — with timing, copy principles, and the API calls to automate it.
When a user signs up for your SaaS, you have about 10 minutes of genuine attention before they move on. A well-designed onboarding email sequence is how you earn back that attention later — guiding them toward the activation moment that separates retained users from churned ones.
A single welcome email is better than nothing, but it has a fundamental problem: it arrives when users don't yet know what they don't know. They just signed up — they haven't hit any obstacles yet, haven't reached any milestones, and can't fully appreciate which features matter.
Email sequences solve this by distributing guidance over time, aligned with where users actually are in the product. Email 1 arrives when they're excited. Email 3 arrives when they might be getting stuck. Email 5 arrives when they're deciding whether to upgrade.
The data backs this up: for most B2B SaaS products, a 5–7 email onboarding sequence that stops when users complete key actions has 2–3x better activation rates than a single welcome email.
Here's the sequence structure that works for most SaaS products. Adjust the specific content to match your activation events.
This email does one thing: tell the user the single most important thing to do next. Not five things — one. The goal is to get them to their first activation event (create a project, invite a teammate, connect an integration).
Subject line examples:
Sent only if the user hasn't completed the activation step. Short, low-pressure, acknowledges that they're probably busy. Repeat the single CTA from email 1 — don't introduce new features.
Subject line examples:
Not a tutorial — a story. How did another customer (similar role, similar problem) use your product to get a specific result? Make the outcome concrete: "reduced support time by 40%", "shipped in one week instead of three." This is the email that overcomes doubt.
Every product has a feature that dramatically changes how people use it — but that most users don't discover on their own. Email 4 introduces that feature. Not all features, just the one that causes "aha" moments most reliably.
By day 12, users have either activated or they haven't. For those who have: introduce a reason to upgrade (feature they need, usage limit they're approaching). For those who haven't: a final helpful prompt before you reduce email frequency. Don't just pitch — offer something useful.
Timing is where most onboarding sequences get it wrong. Two rules:
1. Stop the sequence when users activate. If someone has already completed the step you're nudging them toward, sending that email is counterproductive — it signals you're not paying attention. This requires behavioral triggers, not just time-based sends.
2. Respect business hours. If your product is B2B, send at times when professionals check email: Tuesday–Thursday, 8am–11am in the recipient's timezone. Most email APIs can handle timezone-aware sending if you store the user's timezone on signup.
| Trigger condition | Stop condition | |
|---|---|---|
| 1. Welcome | Signup confirmed | Never (always send) |
| 2. Nudge | +24h, if not activated | User completes activation |
| 3. Case study | +4 days from signup | User cancels or unsubscribes |
| 4. Feature spotlight | +7 days from signup | User cancels or unsubscribes |
| 5. Upgrade offer | +12 days from signup | User upgrades or cancels |
There are two ways to build this: with a purpose-built sequence tool, or by wiring it yourself in application code. Here's the tradeoff:
Services like tinysend let you define the sequence once and enroll users with a single API call. The service handles timing, unsubscribes, and stopping conditions.
async function enrollInOnboarding(user) {
await fetch('https://api.tinysend.co/v1/sequences/onboarding-v2/enroll', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TINYSEND_API_KEY}`,
'Content-Type': 'application/json',
'X-Idempotency-Key': `enroll-onboarding-${user.id}`,
},
body: JSON.stringify({
email: user.email,
variables: {
first_name: user.firstName,
product_url: `https://app.example.com/dashboard`,
activation_url: `https://app.example.com/getting-started`,
}
})
})
}
// Call immediately after email verification
await enrollInOnboarding(newUser)If you're using a job queue (Bull, Sidekiq, Celery), you can schedule each email as a delayed job. More control, more maintenance.
import Queue from 'bull'
const emailQueue = new Queue('emails')
async function scheduleOnboardingSequence(user) {
const delays = [
{ template: 'welcome', delay: 0 },
{ template: 'nudge', delay: 24 * 60 * 60 * 1000 }, // 1 day
{ template: 'case-study', delay: 4 * 24 * 60 * 60 * 1000 }, // 4 days
{ template: 'feature-spotlight', delay: 7 * 24 * 60 * 60 * 1000 }, // 7 days
{ template: 'upgrade-offer', delay: 12 * 24 * 60 * 60 * 1000 }, // 12 days
]
for (const { template, delay } of delays) {
await emailQueue.add(
{ userId: user.id, template },
{ delay, jobId: `onboarding-${user.id}-${template}` } // idempotent job ID
)
}
}
// Job processor
emailQueue.process(async (job) => {
const { userId, template } = job.data
const user = await User.findById(userId)
// Skip if user already activated (for nudge email)
if (template === 'nudge' && user.activatedAt) return
// Skip if user cancelled or unsubscribed
if (user.cancelledAt || user.unsubscribedAt) return
await sendEmail({ to: user.email, template, variables: { user } })
})The right place to enroll users in an onboarding sequence is immediately after email verification — not at initial signup. Reason: if a user never verifies their email, you're sending onboarding emails to a dead address.
app.get('/verify-email', async (req, res) => {
const { token } = req.query
const user = await verifyEmailToken(token)
if (!user) return res.status(400).send('Invalid token')
// Mark verified
await user.update({ emailVerifiedAt: new Date() })
// Enroll in onboarding sequence
await enrollInOnboarding(user)
// Redirect to app
res.redirect('/dashboard?onboarded=true')
})Define your activation event clearly — it should be the single action that predicts retention most strongly. For a project management tool, it might be "created first project AND invited a teammate." Fire a stop event when this happens:
async function onUserActivated(userId) {
const user = await User.findById(userId)
await user.update({ activatedAt: new Date() })
// If using tinysend sequences — unenroll from onboarding
await fetch(`https://api.tinysend.co/v1/sequences/onboarding-v2/unenroll`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.TINYSEND_API_KEY}` },
body: JSON.stringify({ email: user.email })
})
// Now enroll in the post-activation "power user" sequence
await enrollInPowerUserSequence(user)
}Each email should focus on one thing. The "here are 7 features you might not know about" email has a near-zero read rate. Pick the one thing that matters most for that stage of the user journey.
If you're sending a "you haven't started yet" email to someone who's been using your product daily, they'll lose trust in you. Always check activation status before sending emails that assume non-activation.
"Unlock the power of [Product]!" opens much worse than "quick question about [Product]". Onboarding emails should feel like they're from a person who cares about the user's success, not a marketing team hitting KPIs.
Transactional emails can be exempted from CAN-SPAM in some cases, but onboarding sequences that aren't directly tied to account access need an unsubscribe link. Failing this causes spam complaints that hurt deliverability for all your email.
The infrastructure is the easy part. Most teams underinvest in the actual email copy. Write the emails first, validate them with real users, then automate. Bad emails automated efficiently are just bad emails at scale.
Email opens are a vanity metric — open tracking is unreliable since iOS 15. Focus on:
| Metric | What it tells you | Benchmark |
|---|---|---|
| Click rate | Email relevance and CTA quality | 3–8% for onboarding |
| Activation rate by cohort | Whether the sequence is working | Depends heavily on product |
| Unsubscribe rate | Relevance and frequency | <0.5% per email |
| Spam complaint rate | List quality and content relevance | <0.1% (Google threshold) |
| Sequence completion rate | How many users reach email 5 | Tracks disengagement timing |
The most important experiment: A/B test the stop condition. Compare "stop when user activates" vs "send all emails regardless" on activation rates 30 days after signup. This tells you whether your later emails are helpful or counterproductive.
Define your sequence once. Enroll users with a single API call. Stop automatically when they activate.
Start for free