Steven's Knowledge

Getting Started

Send your first transactional email with Resend, verify your domain, build templates with React Email

Getting Started

This page sends a transactional email via Resend — the most developer-friendly modern option. Patterns transfer to Postmark, SendGrid, SES.

Sign Up

  1. Resend.com → sign up.
  2. Add and verify your domain. Resend gives you DNS records (SPF, DKIM); add them to your DNS provider.
  3. Get an API key from the dashboard.

Verification usually takes minutes once DNS propagates.

Send Your First Email

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

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

await resend.emails.send({
  from: 'Acme <noreply@example.com>',     // must be on a verified domain
  to: ['user@example.com'],
  subject: 'Welcome to Acme',
  html: '<h1>Hi!</h1><p>Welcome to Acme.</p>',
  text: 'Hi! Welcome to Acme.',            // plain-text fallback
});

Done. The email lands in user@example.com's inbox.

from must be on a verified domain. Spoofing some random address won't work — DKIM signing requires the domain.

React Email Templates

For real templates, hand-coded HTML emails are painful (table-based layouts, CSS quirks). React Email generates email-safe HTML from JSX:

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

export default function WelcomeEmail({ name, verifyUrl }) {
  return (
    <Html>
      <Head />
      <Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f6f6f6' }}>
        <Container style={{ background: 'white', padding: '20px' }}>
          <Heading>Welcome, {name}</Heading>
          <Text>Thanks for signing up. Click below to verify your email:</Text>
          <Button href={verifyUrl} style={{ background: '#000', color: '#fff', padding: '12px 20px' }}>
            Verify email
          </Button>
          <Hr />
          <Text style={{ fontSize: 12, color: '#888' }}>
            If you didn't sign up, ignore this email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Render to HTML and send:

import { render } from '@react-email/render';
import WelcomeEmail from './emails/welcome';

const html = render(<WelcomeEmail name="Alice" verifyUrl="https://example.com/verify?token=..." />);

await resend.emails.send({
  from: 'Acme <noreply@example.com>',
  to: 'alice@example.com',
  subject: 'Welcome',
  html,
});

React Email handles the inline-CSS, dark-mode tricks, and Outlook tags that hand-written email HTML requires. Preview locally with react-email dev.

Equivalent: Postmark

import { ServerClient } from 'postmark';

const client = new ServerClient(process.env.POSTMARK_API_KEY);

await client.sendEmail({
  From: 'noreply@example.com',
  To: 'alice@example.com',
  Subject: 'Welcome',
  HtmlBody: '<h1>Hi!</h1>',
  TextBody: 'Hi!',
  MessageStream: 'outbound',           // transactional stream
});

Equivalent: SendGrid

import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

await sgMail.send({
  from: 'noreply@example.com',
  to: 'alice@example.com',
  subject: 'Welcome',
  html: '<h1>Hi!</h1>',
  text: 'Hi!',
});

Equivalent: AWS SES

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';

const ses = new SESv2Client({ region: 'us-east-1' });

await ses.send(new SendEmailCommand({
  FromEmailAddress: 'noreply@example.com',
  Destination: { ToAddresses: ['alice@example.com'] },
  Content: {
    Simple: {
      Subject: { Data: 'Welcome' },
      Body: { Html: { Data: '<h1>Hi!</h1>' }, Text: { Data: 'Hi!' } },
    },
  },
}));

SES is the cheapest at scale ($0.10 per 1,000 emails) but you get pure SMTP delivery — no built-in templating, fewer observability features, no marketing tools.

Verifying the Send Worked

The API returns 200 OK once accepted by the provider. That's not delivery confirmation. The email is queued for delivery; bounces and complaints come later via webhooks.

For end-to-end tests:

  • Send to a mailbox you control (real or via Mailtrap).
  • Test the rendered output via the provider's preview tools.
  • Use the provider's testing endpoints (Resend's test API key delivers without sending).

Domain Setup Done Right

When you verify your domain with a transactional provider, you'll add (typical Resend example):

# Resend verification (provider-specific)
resend._domainkey.example.com    TXT    "p=MIGfMA0..."

# SPF — include your provider's authorized servers
example.com    TXT    "v=spf1 include:_spf.resend.com include:_spf.google.com -all"

# DKIM — provider generates a public key
resend._domainkey.example.com    TXT    "v=DKIM1; k=rsa; p=..."

# DMARC — start with monitoring, graduate to enforcement
_dmarc.example.com    TXT    "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"

Begin with p=none to monitor what fails. Graduate to p=quarantine then p=reject once you're confident. DMARC reject is the goal — it makes spoofing your domain difficult.

Subdomain Strategy

A common pattern:

SubdomainUsed for
@example.com (apex)Human-to-human email
@mail.example.comTransactional (signup, alerts)
@news.example.comMarketing / newsletters
@notifications.example.comApp notifications

Why subdomains? A bad marketing campaign hurts the subdomain's reputation, not your apex. Transactional emails keep landing in inboxes even if marketing emails get filtered.

Most providers let you verify and send from multiple subdomains.

Bounces and Webhooks

Once you send, you'll get events back:

delivered → recipient's mail server accepted
opened → recipient opened the email (tracking pixel)
clicked → recipient clicked a link (tracked link)
bounced → mail server rejected (hard or soft)
complained → recipient marked as spam
unsubscribed → clicked your unsubscribe link

Configure a webhook in your provider's dashboard:

// webhook handler
app.post('/webhooks/resend', verifyWebhookSignature, async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'email.bounced':
      await markEmailAsBounced(event.data.to);
      break;
    case 'email.complained':
      await suppressEmail(event.data.to);   // remove from future sends
      break;
    case 'email.unsubscribed':
      await unsubscribeUser(event.data.to);
      break;
  }

  res.sendStatus(200);
});

Suppressing bounced and complained addresses is critical — repeatedly sending to bad addresses tanks your sender reputation.

Tear Down

There's nothing to tear down — your domain stays verified, the API key keeps working.

What's Next

You can send transactional email. Next:

  • Patterns — templates, scheduled sends, transactional vs marketing, multi-channel
  • Best Practices — deliverability, unsubscribe, observability, compliance

On this page