Production Engineering/
Lesson

Email is deceptively hard. Sending an email is easy, getting it to land in the inbox instead of spam is the hard part. Email deliverability is a world of SPF records, DKIM signatures, reputation scores, and blocklists. That's why you use an email service providerWhat is provider?A wrapper component that makes data available to all components nested inside it without passing props manually. rather than sending from your own server.

Transactional vs marketing email

Not all email is the same. The infrastructure and rules differ:

TypeExamplesCharacteristics
TransactionalWelcome, password reset, receipt, alertTriggered by user action, high urgency, must arrive
MarketingNewsletters, promotions, announcementsSent in bulk, recipient can unsubscribe

This lesson focuses on transactional email. Marketing email has additional requirements (CAN-SPAM compliance, unsubscribe links) that are usually handled by tools like Mailchimp or ConvertKit.

02

Sending email with Resend

Resend is the simplest modern email APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses.. Clean interface, good TypeScript support, and it pairs naturally with React Email for templating.

npm install resend
import { Resend } from 'resend';

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

// Send a simple email
async function sendWelcomeEmail(userEmail: string, userName: string) {
  const { data, error } = await resend.emails.send({
    from: 'HACKUP <onboarding@hackup.dev>',  // Must be a verified domain
    to: userEmail,
    subject: 'Welcome to HACKUP',
    html: `<h1>Hey ${userName}!</h1><p>Your account is ready. Start learning.</p>`
  });

  if (error) {
    console.error('Email failed:', error);
    return { success: false };
  }

  return { success: true, id: data?.id };
}
Never throw an unhandled exception when an email fails. Email is important but it's not as important as the action that triggered it. If the welcome email fails, the user still signed up successfully, log the error, alert yourself, and move on.
03

React Email templates

Hardcoded HTML strings in your JavaScript are painful to maintain. React Email lets you write email templates as React components, which you then render to HTML before sending.

npm install @react-email/components react-dom
// emails/WelcomeEmail.tsx
import {
  Html,
  Head,
  Body,
  Container,
  Heading,
  Text,
  Button,
  Hr
} from '@react-email/components';

interface WelcomeEmailProps {
  userName: string;
  dashboardUrl: string;
}

export function WelcomeEmail({ userName, dashboardUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f4f4f4' }}>
        <Container style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
          <Heading>Welcome, {userName}!</Heading>
          <Text>Your account is set up and ready to go.</Text>
          <Button
            href={dashboardUrl}
            style={{ backgroundColor: '#22c55e', color: 'white', padding: '12px 24px' }}
          >
            Go to dashboard
          </Button>
          <Hr />
          <Text style={{ fontSize: '12px', color: '#666' }}>
            You received this because you signed up at hackup.dev.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}
// Render the component to HTML and send it
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/WelcomeEmail';

async function sendWelcomeEmail(user: { email: string; name: string }) {
  const html = render(
    <WelcomeEmail
      userName={user.name}
      dashboardUrl={`${process.env.APP_URL}/dashboard`}
    />
  );

  await resend.emails.send({
    from: 'HACKUP <onboarding@hackup.dev>',
    to: user.email,
    subject: 'Welcome to HACKUP',
    html
  });
}
04

DNSWhat is dns?The system that translates human-readable domain names like google.com into the numerical IP addresses computers use to find each other. configuration for deliverability

Before you can send from your own domain, you need to add DNS records that prove you own it and authorize Resend (or SendGrid) to send on your behalf. Without these, your email will land in spam or be rejected entirely.

# SPF - lists which servers are allowed to send email for your domain
Type:  TXT
Name:  @
Value: v=spf1 include:_spf.resend.com ~all

# DKIM - a cryptographic signature Resend adds to each email
# Resend gives you the exact record to add in their dashboard
Type:  CNAME
Name:  resend._domainkey
Value: [provided by Resend]

# DMARC - tells receiving servers what to do with emails that fail SPF/DKIM
Type:  TXT
Name:  _dmarc
Value: v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
DNS changes take time to propagate, anywhere from a few minutes to 48 hours. Resend's dashboard shows you the verification status. Don't test sending until the domain is verified.
05

SendGrid as an alternative

SendGrid is older and more feature-rich than Resend, but its APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. is more verbose. It's a good choice if you need advanced features like dynamic templates, suppression groups, or very high volume.

const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

async function sendPasswordReset(email: string, resetToken: string) {
  const resetUrl = `${process.env.APP_URL}/reset-password?token=${resetToken}`;

  await sgMail.send({
    to: email,
    from: 'support@yourdomain.com',  // Must be a verified sender
    subject: 'Reset your password',
    templateId: 'd-abc123',           // Optional: SendGrid dynamic template
    dynamicTemplateData: {
      reset_url: resetUrl,
      expiry: '1 hour'
    }
  });
}
06

Quick reference

FeatureResendSendGrid
Free tier3,000 emails/month100 emails/day
React Email supportNativeVia render
TypeScriptExcellentGood
Dynamic templatesBasicAdvanced
Best forModern apps, prototypesHigh volume, enterprise
javascript
// Complete email service
class EmailService {
  constructor() {
    this.resend = new Resend(process.env.RESEND_API_KEY);
  }

  async sendWelcomeEmail(user) {
    try {
      await this.resend.emails.send({
        from: 'Acme <onboarding@acme.com>',
        to: user.email,
        subject: 'Welcome to Acme!',
        html: `
          <h1>Hello ${user.name}!</h1>
          <p>Thank you for signing up.</p>
          <a href="${process.env.APP_URL}/dashboard">
            Go to my account
          </a>
        `
      });
    } catch (error) {
      console.error('Email failed:', error);
      // Don't block user flow if email fails
    }
  }

  async sendPasswordReset(user, token) {
    const resetUrl = `${process.env.APP_URL}/reset-password?token=${token}`;

    await this.resend.emails.send({
      from: 'Acme <support@acme.com>',
      to: user.email,
      subject: 'Password reset',
      html: `
        <p>Click this link to reset your password:</p>
        <a href="${resetUrl}">${resetUrl}</a>
        <p>This link expires in 1 hour.</p>
      `
    });
  }
}