Someone signs up for your SaaS. You send a welcome email. A week later, you check analytics and they never came back.
Nothing dramatic happened. They got busy. The welcome email landed while they were in a meeting. They never made it far enough to see why your product is worth their time.
A single email is not enough. An onboarding sequence -- three emails spaced over the first week -- gives you more chances to reach a new user before they forget you exist.
The usual answer is Customer.io, Drip, or ActiveCampaign. These tools cost $50 to $200 a month before you have your first paying user, and they require a separate integration that lives outside your codebase.
Building it yourself sounds like a project. It is not. If your Next.js SaaS boilerplate already has Vercel Cron and Resend wired up, the core mechanic is three pieces:
onboarding_step column on your user table to track which emails each user has receivedNo third-party tool. No monthly subscription. No parallel contact list to keep in sync.
Three emails is enough for most SaaS products:
Day 1 (24 hours after signup): The setup prompt. Most users signed up before finishing setup. Remind them of the one action that leads to the first value moment -- connecting an account, inviting a teammate, creating their first project.
Day 3: The proof email. Show something concrete: a result they could have if they used the product, a short example of it working, or a case where it saved someone real time. Address the unspoken objection: "I do not know if this is worth my time."
Day 7: The fork-in-the-road email. If they converted, congratulate them. If they have not, acknowledge it directly -- "You signed up a week ago and I am not sure if you ever got started" -- and offer to help.
Add one integer column to your user table:
onboardingStep: integer('onboarding_step').notNull().default(0),
Step 0 means no onboarding emails have been sent. Steps 1, 2, and 3 correspond to the day-1, day-3, and day-7 emails. Incrementing the step after each send prevents double-sends even if the cron job overlaps or retries.
If you followed the background jobs pattern in this boilerplate, you already know the structure: a thin API route that validates the caller, calls a service, and returns. The cron runs daily at a consistent time.
In vercel.json:
{
"crons": [
{ "path": "/api/cron/onboarding-emails", "schedule": "0 9 * * *" }
]
}
The route delegates to onboardingEmailService.sendDueEmails(), which queries three batches:
onboarding_step = 0 and created_at <= now - 24h -- send day-1 emailonboarding_step = 1 and created_at <= now - 3 days -- send day-3 emailonboarding_step = 2 and created_at <= now - 7 days -- send day-7 emailFor each batch: send via Resend, then immediately increment onboarding_step. Always write the increment after the send resolves -- if the send fails, the step stays put and you retry tomorrow. Never increment before sending.
Keep them short. 100 to 150 words per email is enough. One action per email, not a newsletter.
Write the day-1 email as a follow-up to the signup conversation. Write the day-3 email as if you are sending a useful link. Write the day-7 email as if you genuinely want to know whether they got value.
The tone that works: a founder who cares whether the product is useful -- not a marketing department hitting a send quota.
Drag-and-drop sequence builders, contact segmentation, and A/B testing are useful when you have a marketing team and thousands of users. When you are an early-stage founder writing the emails yourself and tweaking them every two weeks, the tools add cost and complexity without adding control.
The Resend + react-email setup this boilerplate ships with handles template rendering. Drizzle ORM handles the query. Vercel Cron handles the schedule. You already have all three -- you are just connecting them.
Add the schema column, generate and run the migration, create the cron route, write three short email templates in emails/, and your onboarding sequence is live.
Your new users are deciding whether to come back during their first week. Get the Next.js SaaS boilerplate and give them three more reasons to say yes.