Resend Email Testing Guide: Testing Transactional Emails with Resend
Resend has quickly become the developer-favorite transactional email API — clean SDK, React Email template support, and a generous free tier. But email is one of the most undertested parts of most applications. Password reset emails that don't send, welcome emails with broken links, and invoice emails with wrong amounts are all bugs that slip through because developers skip email testing.
This guide covers how to test Resend-powered email thoroughly: unit testing your email service, testing React Email templates, and integration testing the full send flow.
What to Test With Resend
| Concern | What to test | Test type |
|---|---|---|
| Email service logic | Correct recipient, subject, template data | Unit |
| React Email templates | HTML output, links, dynamic content | Component test |
| Send triggers | Email fires at the right moment (signup, purchase) | Integration |
| Resend API calls | Correct payload shape | Unit (with mock) |
| Deliverability | Emails actually arrive | Manual / staging |
Unit Testing Your Email Service
The core pattern: inject the Resend client as a dependency, mock it in tests.
Email Service
// src/services/EmailService.ts
import { Resend } from 'resend';
import { WelcomeEmail } from '../emails/WelcomeEmail';
import { OrderConfirmationEmail } from '../emails/OrderConfirmationEmail';
import { PasswordResetEmail } from '../emails/PasswordResetEmail';
import * as React from 'react';
import { render } from '@react-email/render';
type SendResult = { id: string };
export class EmailService {
constructor(private resend: Resend) {}
async sendWelcomeEmail(to: string, name: string): Promise<SendResult> {
const { data, error } = await this.resend.emails.send({
from: 'HelpMeTest <hello@helpmetest.com>',
to,
subject: `Welcome to HelpMeTest, ${name}!`,
react: React.createElement(WelcomeEmail, { name }),
});
if (error) throw new Error(`Failed to send welcome email: ${error.message}`);
return { id: data!.id };
}
async sendPasswordReset(to: string, resetUrl: string): Promise<SendResult> {
if (!resetUrl.startsWith('https://')) {
throw new Error('resetUrl must be an HTTPS URL');
}
const { data, error } = await this.resend.emails.send({
from: 'HelpMeTest <security@helpmetest.com>',
to,
subject: 'Reset your HelpMeTest password',
react: React.createElement(PasswordResetEmail, { resetUrl }),
});
if (error) throw new Error(`Failed to send password reset: ${error.message}`);
return { id: data!.id };
}
async sendOrderConfirmation(
to: string,
order: { id: string; total: number; items: { name: string; qty: number; price: number }[] },
): Promise<SendResult> {
const { data, error } = await this.resend.emails.send({
from: 'HelpMeTest <orders@helpmetest.com>',
to,
subject: `Order confirmed — #${order.id}`,
react: React.createElement(OrderConfirmationEmail, { order }),
});
if (error) throw new Error(`Failed to send order confirmation: ${error.message}`);
return { id: data!.id };
}
}Unit Tests
// src/services/EmailService.test.ts
import { Resend } from 'resend';
import { EmailService } from './EmailService';
jest.mock('resend');
describe('EmailService', () => {
let service: EmailService;
let mockResend: jest.Mocked<Resend>;
beforeEach(() => {
mockResend = new Resend('test-key') as jest.Mocked<Resend>;
mockResend.emails = {
send: jest.fn().mockResolvedValue({
data: { id: 'email-id-123' },
error: null,
}),
} as any;
service = new EmailService(mockResend);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('sendWelcomeEmail', () => {
it('sends to the correct recipient', async () => {
await service.sendWelcomeEmail('alice@example.com', 'Alice');
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({ to: 'alice@example.com' }),
);
});
it('includes the user name in the subject', async () => {
await service.sendWelcomeEmail('alice@example.com', 'Alice');
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({
subject: expect.stringContaining('Alice'),
}),
);
});
it('sends from the correct address', async () => {
await service.sendWelcomeEmail('alice@example.com', 'Alice');
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({ from: 'HelpMeTest <hello@helpmetest.com>' }),
);
});
it('returns the email ID from Resend', async () => {
const result = await service.sendWelcomeEmail('alice@example.com', 'Alice');
expect(result.id).toBe('email-id-123');
});
it('throws when Resend returns an error', async () => {
mockResend.emails.send.mockResolvedValue({
data: null,
error: { message: 'Invalid API key', name: 'validation_error' },
} as any);
await expect(
service.sendWelcomeEmail('alice@example.com', 'Alice'),
).rejects.toThrow('Failed to send welcome email');
});
});
describe('sendPasswordReset', () => {
const validUrl = 'https://app.helpmetest.com/reset?token=abc123';
it('rejects non-HTTPS reset URLs', async () => {
await expect(
service.sendPasswordReset('alice@example.com', 'http://evil.com/reset'),
).rejects.toThrow('must be an HTTPS URL');
});
it('sends to the security from address', async () => {
await service.sendPasswordReset('alice@example.com', validUrl);
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({ from: 'HelpMeTest <security@helpmetest.com>' }),
);
});
});
describe('sendOrderConfirmation', () => {
const order = {
id: 'ORD-001',
total: 5999,
items: [{ name: 'Pro Plan', qty: 1, price: 5999 }],
};
it('includes the order ID in the subject', async () => {
await service.sendOrderConfirmation('buyer@example.com', order);
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({
subject: expect.stringContaining('ORD-001'),
}),
);
});
it('sends from the orders address', async () => {
await service.sendOrderConfirmation('buyer@example.com', order);
expect(mockResend.emails.send).toHaveBeenCalledWith(
expect.objectContaining({ from: 'HelpMeTest <orders@helpmetest.com>' }),
);
});
});
});Testing React Email Templates
React Email templates are React components that render to HTML. Test them like any React component:
// src/emails/WelcomeEmail.tsx
import * as React from 'react';
import { Html, Head, Body, Container, Text, Button, Hr } from '@react-email/components';
interface WelcomeEmailProps {
name: string;
}
export function WelcomeEmail({ name }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif' }}>
<Container>
<Text>Hi {name},</Text>
<Text>
Welcome to HelpMeTest! Your automated testing journey starts today.
</Text>
<Button href="https://app.helpmetest.com/dashboard">
Go to Dashboard
</Button>
<Hr />
<Text style={{ fontSize: '12px', color: '#666' }}>
You're receiving this because you signed up at helpmetest.com.
</Text>
</Container>
</Body>
</Html>
);
}Testing the template:
// src/emails/WelcomeEmail.test.tsx
import * as React from 'react';
import { render } from '@react-email/render';
import { WelcomeEmail } from './WelcomeEmail';
describe('WelcomeEmail', () => {
it('includes the user name', () => {
const html = render(<WelcomeEmail name="Alice" />);
expect(html).toContain('Hi Alice');
});
it('includes the dashboard link', () => {
const html = render(<WelcomeEmail name="Alice" />);
expect(html).toContain('https://app.helpmetest.com/dashboard');
});
it('renders valid HTML', () => {
const html = render(<WelcomeEmail name="Test User" />);
expect(html).toContain('<html');
expect(html).toContain('</html>');
expect(html).not.toContain('undefined');
expect(html).not.toContain('[object Object]');
});
it('escapes special characters in name', () => {
const html = render(<WelcomeEmail name={'<script>alert("xss")</script>'} />);
expect(html).not.toContain('<script>');
expect(html).toContain('<script>');
});
});// src/emails/OrderConfirmationEmail.test.tsx
import * as React from 'react';
import { render } from '@react-email/render';
import { OrderConfirmationEmail } from './OrderConfirmationEmail';
const mockOrder = {
id: 'ORD-123',
total: 5999,
items: [
{ name: 'Pro Plan', qty: 1, price: 5999 },
],
};
describe('OrderConfirmationEmail', () => {
it('displays the order ID', () => {
const html = render(<OrderConfirmationEmail order={mockOrder} />);
expect(html).toContain('ORD-123');
});
it('formats the total as currency', () => {
const html = render(<OrderConfirmationEmail order={mockOrder} />);
// $59.99 formatted
expect(html).toMatch(/\$59\.99|\$59,99|59\.99/);
});
it('lists all order items', () => {
const orderWithMultipleItems = {
...mockOrder,
items: [
{ name: 'Pro Plan', qty: 1, price: 4999 },
{ name: 'Add-on', qty: 2, price: 500 },
],
};
const html = render(<OrderConfirmationEmail order={orderWithMultipleItems} />);
expect(html).toContain('Pro Plan');
expect(html).toContain('Add-on');
});
it('does not expose any undefined values', () => {
const html = render(<OrderConfirmationEmail order={mockOrder} />);
expect(html).not.toContain('undefined');
});
});Integration Testing: Verifying Sends Against Resend
For true integration testing, use Resend's test mode or a dedicated test domain:
// tests/integration/email.integration.test.ts
import { Resend } from 'resend';
import { EmailService } from '../../src/services/EmailService';
// This test uses a real Resend API key and sends to the Resend test inbox
const resend = new Resend(process.env.RESEND_TEST_API_KEY!);
const service = new EmailService(resend);
// Use onboarding@resend.dev as recipient in test mode — Resend captures it
const TEST_RECIPIENT = 'delivered@resend.dev'; // Resend's test address
describe('Email delivery (integration)', () => {
it('sends welcome email and returns a valid ID', async () => {
const result = await service.sendWelcomeEmail(TEST_RECIPIENT, 'Integration Tester');
expect(result.id).toBeDefined();
expect(result.id).toMatch(/^[a-z0-9-]+$/);
}, 15_000);
it('sends password reset email', async () => {
const result = await service.sendPasswordReset(
TEST_RECIPIENT,
'https://app.helpmetest.com/reset?token=test-token',
);
expect(result.id).toBeDefined();
}, 15_000);
});Resend's special test address delivered@resend.dev accepts emails in test mode and marks them as delivered without actually sending them to an inbox. Perfect for CI.
Testing Email Triggers
Test that emails fire at the right point in your application flow:
// src/auth/signup.test.ts
import { signUp } from './signup';
import { EmailService } from '../services/EmailService';
jest.mock('../services/EmailService');
const mockEmailService = {
sendWelcomeEmail: jest.fn().mockResolvedValue({ id: 'email-1' }),
};
describe('signUp flow', () => {
it('sends welcome email after successful registration', async () => {
await signUp({
email: 'newuser@example.com',
password: 'SecurePass123!',
name: 'New User',
});
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledTimes(1);
expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith(
'newuser@example.com',
'New User',
);
});
it('does not send email if registration fails', async () => {
// Simulate duplicate email error
await expect(
signUp({ email: 'existing@example.com', password: 'pass', name: 'Dup' }),
).rejects.toThrow();
expect(mockEmailService.sendWelcomeEmail).not.toHaveBeenCalled();
});
});CI Configuration
# .github/workflows/test.yml
- name: Run email unit tests
run: npm test -- --testPathPattern="email|Email"
- name: Run email integration tests
run: npm test -- --testPathPattern="email.integration"
env:
RESEND_TEST_API_KEY: ${{ secrets.RESEND_TEST_API_KEY }}Keep integration email tests in a separate --testPathPattern so they don't slow down the unit test suite.
Local Email Preview
Resend integrates with React Email's preview server:
npx react-email dev --dir src/emails --port 3001This gives you a local browser preview of every email template — useful during development, not a replacement for automated tests.
Snapshot Testing Templates
For templates that change infrequently, snapshot tests catch unexpected regressions:
it('matches snapshot', () => {
const html = render(<WelcomeEmail name="Snapshot User" />);
expect(html).toMatchSnapshot();
});Update snapshots deliberately: jest --updateSnapshot. This ensures your team consciously reviews template changes rather than discovering them in production.
Key Principles
Never test with real production API keys in CI. Use a dedicated test API key with your RESEND_TEST_API_KEY environment variable. Resend's test mode costs nothing and doesn't send real emails.
Test the payload, not just the call. Verify from, to, subject, and the presence of key content in the rendered template. Don't just assert that send was called once.
Test XSS safety in templates. User-provided content (names, order descriptions) goes into email templates. Assert that React Email correctly escapes special characters.
Test email triggers at integration boundaries. A unit test that the email service sends correctly is good. An integration test that the signup handler fires the welcome email is better — it catches wiring bugs.
For teams monitoring email deliverability in production, HelpMeTest can run scheduled tests that trigger transactional emails and verify they arrive — catching Resend API key expiration, from-domain misconfiguration, and other delivery issues before users report them.