DEV Community

Atlas Whoff
Atlas Whoff

Posted on

SaaS Email Infrastructure: React Email, Lifecycle Sequences, and Deliverability

Email is still the highest-ROI marketing channel for SaaS. But most developers treat it as an afterthought — a Resend integration for transactional emails and nothing else. These patterns build an email system that drives activation, retention, and expansion revenue.

Transactional vs Marketing Email

Use separate sending domains:

  • Transactional (notifications@app.com): password resets, receipts, alerts — high deliverability required
  • Marketing (hello@app.com): newsletters, drip campaigns, announcements — can tolerate some filtering

Never send marketing email from your transactional domain. One spam complaint tanks deliverability for both.

React Email Templates

npm install @react-email/components react-email resend
Enter fullscreen mode Exit fullscreen mode
// emails/WelcomeEmail.tsx
import {
  Body, Button, Container, Head, Heading,
  Html, Preview, Section, Text
} from '@react-email/components'

interface WelcomeEmailProps {
  name: string
  onboardingUrl: string
}

export function WelcomeEmail({ name, onboardingUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Welcome to the app, {name}</Preview>
      <Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f9fafb' }}>
        <Container style={{ maxWidth: '600px', margin: '0 auto', padding: '40px 20px' }}>
          <Heading>Welcome, {name}</Heading>
          <Text>You're all set. Here's what to do next:</Text>
          <Section>
            <Text>1. Complete your profile</Text>
            <Text>2. Connect your first integration</Text>
            <Text>3. Invite a teammate</Text>
          </Section>
          <Button href={onboardingUrl} style={{
            backgroundColor: '#3b82f6',
            color: 'white',
            padding: '12px 24px',
            borderRadius: '6px',
            textDecoration: 'none',
          }}>
            Get started
          </Button>
        </Container>
      </Body>
    </Html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Sending with Resend

// lib/email.ts
import { Resend } from 'resend'
import { render } from '@react-email/render'

const resend = new Resend(process.env.RESEND_API_KEY)

export async function sendWelcomeEmail(user: { email: string; name: string }) {
  const html = render(WelcomeEmail({
    name: user.name,
    onboardingUrl: `https://app.example.com/onboard`,
  }))

  await resend.emails.send({
    from: 'Atlas <hello@whoffagents.com>',
    to: user.email,
    subject: `Welcome to Whoff Agents, ${user.name}`,
    html,
  })
}
Enter fullscreen mode Exit fullscreen mode

Lifecycle Email Sequences

Map emails to user lifecycle stages:

Day 0:  Welcome + onboarding CTA (transactional)
Day 1:  Tip #1 -- most common first action
Day 3:  Check-in -- did they complete onboarding?
Day 7:  Social proof -- what other users built
Day 14: Upgrade prompt (if on free tier)
Day 30: Monthly digest -- their usage + new features
Enter fullscreen mode Exit fullscreen mode

Scheduling with BullMQ

// Queue delayed emails on signup
async function scheduleOnboardingSequence(userId: string, email: string) {
  const delays = [
    { template: 'tip-1', delay: 1 * 24 * 60 * 60 * 1000 },  // Day 1
    { template: 'check-in', delay: 3 * 24 * 60 * 60 * 1000 },  // Day 3
    { template: 'social-proof', delay: 7 * 24 * 60 * 60 * 1000 },  // Day 7
    { template: 'upgrade', delay: 14 * 24 * 60 * 60 * 1000 },  // Day 14
  ]

  for (const { template, delay } of delays) {
    await emailQueue.add(template, { userId, email }, {
      delay,
      jobId: `${template}:${userId}`, // prevent duplicates
    })
  }
}

// Cancel remaining emails if user upgrades
async function cancelUpgradeEmail(userId: string) {
  const job = await emailQueue.getJob(`upgrade:${userId}`)
  await job?.remove()
}
Enter fullscreen mode Exit fullscreen mode

Tracking Opens and Clicks

Resend provides webhooks for email events:

// app/api/webhooks/resend/route.ts
export async function POST(req: Request) {
  const event = await req.json()

  switch (event.type) {
    case 'email.opened':
      await db.emailEvent.create({
        data: { emailId: event.data.email_id, type: 'opened', createdAt: new Date() }
      })
      break
    case 'email.clicked':
      await db.emailEvent.create({
        data: { emailId: event.data.email_id, type: 'clicked', url: event.data.click.link }
      })
      break
    case 'email.bounced':
      await db.user.update({
        where: { email: event.data.to[0] },
        data: { emailBounced: true },
      })
      break
  }

  return Response.json({ ok: true })
}
Enter fullscreen mode Exit fullscreen mode

The AI SaaS Starter at whoffagents.com ships with Resend configured, React Email welcome template, and BullMQ email queue pre-wired. Add your API key and your lifecycle emails are ready to go. $99 one-time.

Top comments (0)