Next.js course Β· Module 9: Integrations and Advanced Features

Next.js z Resend i React Email - Nowoczesny system mailowy

14 min read
In this lesson13

In Quantum Metropolis, where communication happens at the speed of light, you will discover the combination of Next.js with Resend and React Email - the most modern stack for sending emails. This combination allows you to create beautiful, responsive emails and send them with enterprise-grade reliability.

Czym jest Resend?

Resend is a modern email sending platform, created by a team of developers for developers:

  • Developer Experience - clean API and excellent documentation
  • React Email integration - native support for React components
  • High deliverability - advanced deliverability mechanisms
  • Analytics - detailed delivery statistics
  • Webhooks - real-time powiadomienia o statusie emaili
  • Templates - template system with React

What is React Email?

React Email is a library for creating emails using React:

  • Component-based - creating emails like regular React components
  • TypeScript support - full type support
  • Preview - email preview in the browser
  • Cross-client compatibility - compatibility with all email clients
  • Export to HTML - conversion to static HTML

Installation and configuration

Step 1: Create a Resend account

  1. Sign up at resend.com
  2. Verify your domain or use a test domain
  3. Create an API key in Dashboard β†’ API Keys

Step 2: Package installation

1# Install Resend and React Email
2npm install resend react-email @react-email/components
3
4# For development add React Email CLI
5npm install -D @react-email/cli

Step 3: Environment variables configuration

Create the file .env.local:

1# Resend API Key
2RESEND_API_KEY=re_123456789
3
4# Email settings
5FROM_EMAIL=noreply@yourdomain.com
6REPLY_TO_EMAIL=support@yourdomain.com

Step 4: Setup React Email

Add scripts to package.json:

1{
2  "scripts": {
3    "email:dev": "email dev",
4    "email:export": "email export"
5  }
6}

Create the react-email.config.js configuration:

1module.exports = {
2  outDir: './out',
3  pretty: true,
4  plainText: true,
5};

Basic integration

Step 1: Initializing Resend

Create a Resend instance:

1// lib/resend.ts
2import { Resend } from 'resend';
3
4if (!process.env.RESEND_API_KEY) {
5  throw new Error('RESEND_API_KEY is not defined');
6}
7
8export const resend = new Resend(process.env.RESEND_API_KEY);

Step 2: Folder structure

Create the structure for emails:

1emails/
2β”œβ”€β”€ components/
3β”‚   β”œβ”€β”€ Header.tsx
4β”‚   β”œβ”€β”€ Footer.tsx
5β”‚   └── Button.tsx
6β”œβ”€β”€ templates/
7β”‚   β”œβ”€β”€ welcome.tsx
8β”‚   β”œβ”€β”€ password-reset.tsx
9β”‚   └── order-confirmation.tsx
10└── index.ts

Creating email components

Basic components

1// emails/components/Header.tsx
2import { Img, Section, Text } from '@react-email/components';
3
4interface HeaderProps {
5  logoUrl?: string;
6  companyName: string;
7}
8
9export function Header({ logoUrl, companyName }: HeaderProps) {
10  return (
11    <Section style={headerStyle}>
12      {logoUrl && (
13        <Img
14          src={logoUrl}
15          width="150"
16          height="50"
17          alt={companyName}
18          style={logoStyle}
19        />
20      )}
21      <Text style={companyStyle}>{companyName}</Text>
22    </Section>
23  );
24}
25
26const headerStyle = {
27  backgroundColor: '#f8f9fa',
28  padding: '20px',
29  textAlign: 'center' as const,
30};
31
32const logoStyle = {
33  margin: '0 auto',
34  marginBottom: '10px',
35};
36
37const companyStyle = {
38  fontSize: '24px',
39  fontWeight: 'bold',
40  color: '#333333',
41  margin: '0',
42};
1// emails/components/Button.tsx
2import { Button as EmailButton } from '@react-email/components';
3
4interface ButtonProps {
5  href: string;
6  children: React.ReactNode;
7  variant?: 'primary' | 'secondary';
8}
9
10export function Button({ href, children, variant = 'primary' }: ButtonProps) {
11  const style = variant === 'primary' ? primaryButtonStyle : secondaryButtonStyle;
12
13  return (
14    <EmailButton href={href} style={style}>
15      {children}
16    </EmailButton>
17  );
18}
19
20const primaryButtonStyle = {
21  backgroundColor: '#007bff',
22  color: '#ffffff',
23  padding: '12px 24px',
24  borderRadius: '6px',
25  textDecoration: 'none',
26  display: 'inline-block',
27  fontWeight: 'bold',
28};
29
30const secondaryButtonStyle = {
31  backgroundColor: '#6c757d',
32  color: '#ffffff',
33  padding: '12px 24px',
34  borderRadius: '6px',
35  textDecoration: 'none',
36  display: 'inline-block',
37  fontWeight: 'bold',
38};
1// emails/components/Footer.tsx
2import { Section, Text, Link } from '@react-email/components';
3
4interface FooterProps {
5  companyName: string;
6  unsubscribeUrl?: string;
7  address?: string;
8}
9
10export function Footer({ companyName, unsubscribeUrl, address }: FooterProps) {
11  return (
12    <Section style={footerStyle}>
13      <Text style={footerTextStyle}>
14        Β© 2024 {companyName}. All rights reserved.
15      </Text>
16
17      {address && (
18        <Text style={addressStyle}>
19          {address}
20        </Text>
21      )}
22
23      {unsubscribeUrl && (
24        <Text style={unsubscribeStyle}>
25          <Link href={unsubscribeUrl} style={linkStyle}>
26            Unsubscribe from newsletter
27          </Link>
28        </Text>
29      )}
30    </Section>
31  );
32}
33
34const footerStyle = {
35  backgroundColor: '#f8f9fa',
36  padding: '20px',
37  textAlign: 'center' as const,
38  marginTop: '40px',
39};
40
41const footerTextStyle = {
42  fontSize: '14px',
43  color: '#6c757d',
44  margin: '0 0 10px',
45};
46
47const addressStyle = {
48  fontSize: '12px',
49  color: '#6c757d',
50  margin: '0 0 10px',
51};
52
53const unsubscribeStyle = {
54  fontSize: '12px',
55  color: '#6c757d',
56  margin: '0',
57};
58
59const linkStyle = {
60  color: '#007bff',
61  textDecoration: 'underline',
62};

Email templates

Welcome email

1// emails/templates/welcome.tsx
2import {
3  Html,
4  Head,
5  Body,
6  Container,
7  Section,
8  Text,
9  Heading,
10  Hr,
11} from '@react-email/components';
12import { Header } from '../components/Header';
13import { Button } from '../components/Button';
14import { Footer } from '../components/Footer';
15
16interface WelcomeEmailProps {
17  userFirstName: string;
18  loginUrl: string;
19  companyName: string;
20}
21
22export default function WelcomeEmail({
23  userFirstName,
24  loginUrl,
25  companyName,
26}: WelcomeEmailProps) {
27  return (
28    <Html>
29      <Head />
30      <Body style={bodyStyle}>
31        <Container style={containerStyle}>
32          <Header companyName={companyName} />
33
34          <Section style={contentStyle}>
35            <Heading style={headingStyle}>
36              Hello, {userFirstName}!
37            </Heading>
38
39            <Text style={textStyle}>
40              Thanks for signing up to {companyName}! We are excited
41              that you have joined our community.
42            </Text>
43
44            <Text style={textStyle}>
45              To start using our platform, click the button below:
46            </Text>
47
48            <Section style={buttonSectionStyle}>
49              <Button href={loginUrl}>
50                Log in to your account
51              </Button>
52            </Section>
53
54            <Hr style={hrStyle} />
55
56            <Text style={helpTextStyle}>
57              If you have questions, feel free to <a href="mailto:support@example.com" style={linkStyle}>contact us</a>.
58            </Text>
59          </Section>
60
61          <Footer
62            companyName={companyName}
63            address="123 Example St, 00-000 Warsaw"
64          />
65        </Container>
66      </Body>
67    </Html>
68  );
69}
70
71const bodyStyle = {
72  backgroundColor: '#ffffff',
73  fontFamily: 'Arial, sans-serif',
74};
75
76const containerStyle = {
77  maxWidth: '600px',
78  margin: '0 auto',
79};
80
81const contentStyle = {
82  padding: '40px 20px',
83};
84
85const headingStyle = {
86  fontSize: '28px',
87  color: '#333333',
88  textAlign: 'center' as const,
89  marginBottom: '30px',
90};
91
92const textStyle = {
93  fontSize: '16px',
94  color: '#333333',
95  lineHeight: '1.6',
96  marginBottom: '20px',
97};
98
99const buttonSectionStyle = {
100  textAlign: 'center' as const,
101  marginTop: '30px',
102  marginBottom: '30px',
103};
104
105const hrStyle = {
106  borderColor: '#e9ecef',
107  margin: '30px 0',
108};
109
110const helpTextStyle = {
111  fontSize: '14px',
112  color: '#6c757d',
113  textAlign: 'center' as const,
114};
115
116const linkStyle = {
117  color: '#007bff',
118  textDecoration: 'underline',
119};

Password reset email

1// emails/templates/password-reset.tsx
2import {
3  Html,
4  Head,
5  Body,
6  Container,
7  Section,
8  Text,
9  Heading,
10  Code,
11} from '@react-email/components';
12import { Header } from '../components/Header';
13import { Button } from '../components/Button';
14import { Footer } from '../components/Footer';
15
16interface PasswordResetEmailProps {
17  userFirstName: string;
18  resetUrl: string;
19  resetCode?: string;
20  companyName: string;
21  expirationTime: string;
22}
23
24export default function PasswordResetEmail({
25  userFirstName,
26  resetUrl,
27  resetCode,
28  companyName,
29  expirationTime,
30}: PasswordResetEmailProps) {
31  return (
32    <Html>
33      <Head />
34      <Body style={bodyStyle}>
35        <Container style={containerStyle}>
36          <Header companyName={companyName} />
37
38          <Section style={contentStyle}>
39            <Heading style={headingStyle}>
40              Password reset
41            </Heading>
42
43            <Text style={textStyle}>
44              Hello {userFirstName},
45            </Text>
46
47            <Text style={textStyle}>
48              We received a request to reset the password for your account at {companyName}.
49              If it wasn't you, ignore this email.
50            </Text>
51
52            <Section style={buttonSectionStyle}>
53              <Button href={resetUrl}>
54                Reset password
55              </Button>
56            </Section>
57
58            {resetCode && (
59              <Section style={codeSectionStyle}>
60                <Text style={codeTextStyle}>
61                  Or use this code:
62                </Text>
63                <Code style={codeStyle}>{resetCode}</Code>
64              </Section>
65            )}
66
67            <Text style={warningTextStyle}>
68              This link will expire in {expirationTime}.
69            </Text>
70
71            <Text style={securityTextStyle}>
72              For security reasons, if you did not request a password reset,
73              <a href="mailto:security@example.com" style={linkStyle}> contact us immediately</a>.
74            </Text>
75          </Section>
76
77          <Footer companyName={companyName} />
78        </Container>
79      </Body>
80    </Html>
81  );
82}
83
84const bodyStyle = {
85  backgroundColor: '#ffffff',
86  fontFamily: 'Arial, sans-serif',
87};
88
89const containerStyle = {
90  maxWidth: '600px',
91  margin: '0 auto',
92};
93
94const contentStyle = {
95  padding: '40px 20px',
96};
97
98const headingStyle = {
99  fontSize: '28px',
100  color: '#333333',
101  textAlign: 'center' as const,
102  marginBottom: '30px',
103};
104
105const textStyle = {
106  fontSize: '16px',
107  color: '#333333',
108  lineHeight: '1.6',
109  marginBottom: '20px',
110};
111
112const buttonSectionStyle = {
113  textAlign: 'center' as const,
114  marginTop: '30px',
115  marginBottom: '30px',
116};
117
118const codeSectionStyle = {
119  textAlign: 'center' as const,
120  marginBottom: '30px',
121  padding: '20px',
122  backgroundColor: '#f8f9fa',
123  borderRadius: '8px',
124};
125
126const codeTextStyle = {
127  fontSize: '14px',
128  color: '#6c757d',
129  marginBottom: '10px',
130};
131
132const codeStyle = {
133  fontSize: '24px',
134  fontWeight: 'bold',
135  letterSpacing: '4px',
136  color: '#007bff',
137};
138
139const warningTextStyle = {
140  fontSize: '14px',
141  color: '#dc3545',
142  textAlign: 'center' as const,
143  marginBottom: '20px',
144  padding: '15px',
145  backgroundColor: '#f8d7da',
146  borderRadius: '6px',
147};
148
149const securityTextStyle = {
150  fontSize: '12px',
151  color: '#6c757d',
152  textAlign: 'center' as const,
153};
154
155const linkStyle = {
156  color: '#007bff',
157  textDecoration: 'underline',
158};

API Routes for sending emails

Basic API

1// app/api/send-email/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { resend } from '@/lib/resend';
4import WelcomeEmail from '@/emails/templates/welcome';
5import PasswordResetEmail from '@/emails/templates/password-reset';
6
7export async function POST(request: NextRequest) {
8  try {
9    const { type, to, data } = await request.json();
10
11    // Basic validation
12    if (!type || !to || !data) {
13      return NextResponse.json(
14        { error: 'Missing required fields: type, to, data' },
15        { status: 400 }
16      );
17    }
18
19    let emailComponent;
20    let subject;
21
22    // Template selection based on type
23    switch (type) {
24      case 'welcome':
25        emailComponent = WelcomeEmail({
26          userFirstName: data.userFirstName,
27          loginUrl: data.loginUrl,
28          companyName: data.companyName || 'Our Company',
29        });
30        subject = `Welcome to ${data.companyName || 'Our Company'}!`;
31        break;
32
33      case 'password-reset':
34        emailComponent = PasswordResetEmail({
35          userFirstName: data.userFirstName,
36          resetUrl: data.resetUrl,
37          resetCode: data.resetCode,
38          companyName: data.companyName || 'Our Company',
39          expirationTime: data.expirationTime || '24 hours',
40        });
41        subject = 'Password reset';
42        break;
43
44      default:
45        return NextResponse.json(
46          { error: `Unknown email type: ${type}` },
47          { status: 400 }
48        );
49    }
50
51    // Sending the email
52    const result = await resend.emails.send({
53      from: process.env.FROM_EMAIL!,
54      to: Array.isArray(to) ? to : [to],
55      subject,
56      react: emailComponent,
57      replyTo: process.env.REPLY_TO_EMAIL,
58    });
59
60    return NextResponse.json({
61      success: true,
62      messageId: result.data?.id
63    });
64
65  } catch (error) {
66    console.error('Email sending error:', error);
67    return NextResponse.json(
68      { error: 'Failed to send email' },
69      { status: 500 }
70    );
71  }
72}

Sending an email with attachments

1// app/api/send-email-with-attachment/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { resend } from '@/lib/resend';
4
5export async function POST(request: NextRequest) {
6  try {
7    const formData = await request.formData();
8    const to = formData.get('to') as string;
9    const subject = formData.get('subject') as string;
10    const htmlContent = formData.get('html') as string;
11    const attachment = formData.get('attachment') as File;
12
13    if (!to || !subject || !htmlContent) {
14      return NextResponse.json(
15        { error: 'Missing required fields' },
16        { status: 400 }
17      );
18    }
19
20    const emailData: any = {
21      from: process.env.FROM_EMAIL!,
22      to: [to],
23      subject,
24      html: htmlContent,
25    };
26
27    // Adding an attachment if one exists
28    if (attachment) {
29      const buffer = await attachment.arrayBuffer();
30      emailData.attachments = [
31        {
32          filename: attachment.name,
33          content: Buffer.from(buffer),
34        },
35      ];
36    }
37
38    const result = await resend.emails.send(emailData);
39
40    return NextResponse.json({
41      success: true,
42      messageId: result.data?.id
43    });
44
45  } catch (error) {
46    console.error('Email sending error:', error);
47    return NextResponse.json(
48      { error: 'Failed to send email' },
49      { status: 500 }
50    );
51  }
52}

Utility functions

Email service

1// lib/email-service.ts
2import { resend } from '@/lib/resend';
3import WelcomeEmail from '@/emails/templates/welcome';
4import PasswordResetEmail from '@/emails/templates/password-reset';
5
6interface EmailOptions {
7  to: string | string[];
8  subject?: string;
9  replyTo?: string;
10}
11
12interface WelcomeEmailData {
13  userFirstName: string;
14  loginUrl: string;
15  companyName?: string;
16}
17
18interface PasswordResetEmailData {
19  userFirstName: string;
20  resetUrl: string;
21  resetCode?: string;
22  companyName?: string;
23  expirationTime?: string;
24}
25
26export class EmailService {
27  private static async sendEmail(
28    component: React.ReactElement,
29    options: EmailOptions
30  ) {
31    try {
32      const result = await resend.emails.send({
33        from: process.env.FROM_EMAIL!,
34        to: Array.isArray(options.to) ? options.to : [options.to],
35        subject: options.subject!,
36        react: component,
37        replyTo: options.replyTo || process.env.REPLY_TO_EMAIL,
38      });
39
40      return { success: true, messageId: result.data?.id };
41    } catch (error) {
42      console.error('Email sending failed:', error);
43      throw new Error('Failed to send email');
44    }
45  }
46
47  static async sendWelcomeEmail(
48    data: WelcomeEmailData,
49    options: EmailOptions
50  ) {
51    const component = WelcomeEmail({
52      ...data,
53      companyName: data.companyName || 'Our Company',
54    });
55
56    return this.sendEmail(component, {
57      ...options,
58      subject: options.subject || `Welcome to ${data.companyName || 'Our Company'}!`,
59    });
60  }
61
62  static async sendPasswordResetEmail(
63    data: PasswordResetEmailData,
64    options: EmailOptions
65  ) {
66    const component = PasswordResetEmail({
67      ...data,
68      companyName: data.companyName || 'Our Company',
69      expirationTime: data.expirationTime || '24 hours',
70    });
71
72    return this.sendEmail(component, {
73      ...options,
74      subject: options.subject || 'Password reset',
75    });
76  }
77
78  static async sendBulkEmails(
79    recipients: string[],
80    component: React.ReactElement,
81    subject: string
82  ) {
83    const promises = recipients.map(email =>
84      this.sendEmail(component, { to: email, subject })
85    );
86
87    try {
88      const results = await Promise.allSettled(promises);
89      const successful = results.filter(r => r.status === 'fulfilled').length;
90      const failed = results.filter(r => r.status === 'rejected').length;
91
92      return { successful, failed, total: recipients.length };
93    } catch (error) {
94      throw new Error('Bulk email sending failed');
95    }
96  }
97}

Email validation

1// lib/email-validation.ts
2import { z } from 'zod';
3
4export const emailSchema = z.object({
5  type: z.enum(['welcome', 'password-reset', 'order-confirmation']),
6  to: z.union([
7    z.string().email(),
8    z.array(z.string().email()).min(1)
9  ]),
10  data: z.object({
11    userFirstName: z.string().min(1),
12    companyName: z.string().optional(),
13  }).and(
14    z.union([
15      z.object({
16        loginUrl: z.string().url(),
17      }),
18      z.object({
19        resetUrl: z.string().url(),
20        resetCode: z.string().optional(),
21        expirationTime: z.string().optional(),
22      }),
23    ])
24  ),
25});
26
27export function validateEmailRequest(data: unknown) {
28  return emailSchema.parse(data);
29}

Hooks for component integration

useEmailSender hook

1// hooks/useEmailSender.ts
2import { useState } from 'react';
3
4interface EmailData {
5  type: string;
6  to: string | string[];
7  data: Record<string, any>;
8}
9
10export function useEmailSender() {
11  const [loading, setLoading] = useState(false);
12  const [error, setError] = useState<string | null>(null);
13
14  const sendEmail = async (emailData: EmailData) => {
15    setLoading(true);
16    setError(null);
17
18    try {
19      const response = await fetch('/api/send-email', {
20        method: 'POST',
21        headers: {
22          'Content-Type': 'application/json',
23        },
24        body: JSON.stringify(emailData),
25      });
26
27      const result = await response.json();
28
29      if (!response.ok) {
30        throw new Error(result.error || 'Failed to send email');
31      }
32
33      return result;
34    } catch (err) {
35      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
36      setError(errorMessage);
37      throw err;
38    } finally {
39      setLoading(false);
40    }
41  };
42
43  return { sendEmail, loading, error };
44}

Example usage in a component

1// components/WelcomeEmailButton.tsx
2'use client';
3
4import { useEmailSender } from '@/hooks/useEmailSender';
5
6interface WelcomeEmailButtonProps {
7  userEmail: string;
8  userFirstName: string;
9}
10
11export function WelcomeEmailButton({ userEmail, userFirstName }: WelcomeEmailButtonProps) {
12  const { sendEmail, loading, error } = useEmailSender();
13
14  const handleSendWelcomeEmail = async () => {
15    try {
16      await sendEmail({
17        type: 'welcome',
18        to: userEmail,
19        data: {
20          userFirstName,
21          loginUrl: `${window.location.origin}/login`,
22          companyName: 'Metropolia Quantum',
23        },
24      });
25
26      alert('Welcome email has been sent!');
27    } catch (error) {
28      alert('Error while sending email');
29    }
30  };
31
32  return (
33    <div>
34      <button
35        onClick={handleSendWelcomeEmail}
36        disabled={loading}
37        className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
38      >
39        {loading ? 'Sending...' : 'Send welcome email'}
40      </button>
41
42      {error && (
43        <p className="text-red-600 text-sm mt-2">{error}</p>
44      )}
45    </div>
46  );
47}

Webhooks and analytics

Webhook handler

1// app/api/webhooks/resend/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export async function POST(request: NextRequest) {
5  try {
6    const payload = await request.json();
7    const { type, data } = payload;
8
9    // Event logging
10    console.log(`Resend webhook: ${type}`, data);
11
12    switch (type) {
13      case 'email.sent':
14        // Email has been sent
15        await handleEmailSent(data);
16        break;
17
18      case 'email.delivered':
19        // Email has been delivered
20        await handleEmailDelivered(data);
21        break;
22
23      case 'email.bounced':
24        // Email has bounced
25        await handleEmailBounced(data);
26        break;
27
28      case 'email.complained':
29        // Email was reported as spam
30        await handleEmailComplained(data);
31        break;
32
33      default:
34        console.log(`Unhandled webhook type: ${type}`);
35    }
36
37    return NextResponse.json({ received: true });
38  } catch (error) {
39    console.error('Webhook error:', error);
40    return NextResponse.json(
41      { error: 'Webhook processing failed' },
42      { status: 500 }
43    );
44  }
45}
46
47async function handleEmailSent(data: any) {
48  // Logic after email has been sent
49  console.log('Email sent:', data.email_id);
50}
51
52async function handleEmailDelivered(data: any) {
53  // Logic after email has been delivered
54  console.log('Email delivered:', data.email_id);
55}
56
57async function handleEmailBounced(data: any) {
58  // Logic after email has bounced
59  console.log('Email bounced:', data.email_id, data.reason);
60
61  // You can update user status in the database
62  // await updateUserEmailStatus(data.to, 'bounced');
63}
64
65async function handleEmailComplained(data: any) {
66  // Logic after spam report
67  console.log('Email complained:', data.email_id);
68
69  // Automatic unsubscribe from the newsletter
70  // await unsubscribeUser(data.to);
71}

Development and testing

Email preview

Start the preview server:

1npm run email:dev

Go to http://localhost:3000 to preview all emails.

Export to HTML

1npm run email:export

Testing in various clients

1// scripts/test-email-clients.ts
2import { render } from '@react-email/render';
3import WelcomeEmail from '../emails/templates/welcome';
4
5async function testEmailRendering() {
6  const html = render(WelcomeEmail({
7    userFirstName: 'Jan',
8    loginUrl: 'https://example.com/login',
9    companyName: 'Test Company',
10  }));
11
12  // Save HTML to file for testing
13  const fs = require('fs');
14  fs.writeFileSync('./test-output/welcome-email.html', html);
15
16  console.log('Email HTML generated for testing');
17}
18
19testEmailRendering();

Best practices

1. Personalization

1// Always use user data
2const personalizedSubject = `Hi ${firstName}, check out your new features!`;

2. Responsive design

1// Use responsive styles
2const responsiveStyle = {
3  width: '100%',
4  maxWidth: '600px',
5  '@media (max-width: 600px)': {
6    maxWidth: '100%',
7    padding: '20px 10px',
8  },
9};

3. A/B testing

1// Test different email versions
2const subjectVariants = [
3  'Welcome to our application!',
4  'Your account has been created',
5  'Let us begin the adventure together!',
6];
7
8const randomSubject = subjectVariants[Math.floor(Math.random() * subjectVariants.length)];

4. Rate limiting

1// Limit the number of emails per user
2const DAILY_EMAIL_LIMIT = 10;
3
4async function checkEmailLimit(userId: string) {
5  // Check how many emails have been sent today to this user
6  // Implementation depends on the database
7}

Summary

Combining Next.js with Resend and React Email creates a powerful stack for sending emails:

  1. React Email - component-based email creation
  2. Resend - reliable delivery
  3. TypeScript - type safety
  4. Webhooks - real-time analytics
  5. Preview - easy development

This combination lets you build professional email marketing systems, transactional notifications, and user communications.

Code for this lesson: App.tsx
1import React, { useState } from 'react';
2
3interface EmailTemplate {
4  id: string;
5  name: string;
6  subject: string;
7  preview: React.ReactNode;
8}
9
10const WelcomeEmailPreview = () => (
11  <div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '500px', margin: '0 auto', background: '#fff', color: '#333', borderRadius: '8px', overflow: 'hidden' }}>
12    <div style={{ background: 'linear-gradient(135deg, #7c4dff, #64ffda)', padding: '30px', textAlign: 'center' }}>
13      <h1 style={{ color: '#fff', margin: 0, fontSize: '24px' }}>Welcome to Quantum Metropolis!</h1>
14    </div>
15    <div style={{ padding: '24px' }}>
16      <p>Hello <strong>{'{{name}}'}</strong>,</p>
17      <p>Your account has been activated. Here is how to get started:</p>
18      <ol style={{ lineHeight: '2' }}>
19        <li>Complete your profile</li>
20        <li>Review the documentation</li>
21        <li>Create your first project</li>
22      </ol>
23      <div style={{ textAlign: 'center', margin: '20px 0' }}>
24        <span style={{ background: '#7c4dff', color: '#fff', padding: '12px 24px', borderRadius: '6px', display: 'inline-block' }}>Get Started</span>
25      </div>
26    </div>
27    <div style={{ background: '#f5f5f5', padding: '16px', textAlign: 'center', fontSize: '12px', color: '#888' }}>
28      Quantum Metropolis Β© 2025
29    </div>
30  </div>
31);
32
33const ResetPasswordPreview = () => (
34  <div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '500px', margin: '0 auto', background: '#fff', color: '#333', borderRadius: '8px', overflow: 'hidden' }}>
35    <div style={{ background: '#e94560', padding: '24px', textAlign: 'center' }}>
36      <h1 style={{ color: '#fff', margin: 0, fontSize: '20px' }}>Password Reset</h1>
37    </div>
38    <div style={{ padding: '24px' }}>
39      <p>We received a request to reset the password for account <strong>{'{{email}}'}</strong>.</p>
40      <p>Click the button below to set a new password. The link expires in 24h.</p>
41      <div style={{ textAlign: 'center', margin: '24px 0' }}>
42        <span style={{ background: '#e94560', color: '#fff', padding: '12px 24px', borderRadius: '6px', display: 'inline-block' }}>Reset Password</span>
43      </div>
44      <p style={{ fontSize: '13px', color: '#888' }}>If you did not request a reset, ignore this email.</p>
45    </div>
46  </div>
47);
48
49const InvoicePreview = () => (
50  <div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '500px', margin: '0 auto', background: '#fff', color: '#333', borderRadius: '8px', overflow: 'hidden' }}>
51    <div style={{ background: '#0f3460', padding: '24px', textAlign: 'center' }}>
52      <h1 style={{ color: '#fff', margin: 0, fontSize: '20px' }}>Order Confirmation</h1>
53    </div>
54    <div style={{ padding: '24px' }}>
55      <p>Thank you for your purchase! Order <strong>#QM-2025-0042</strong></p>
56      <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '16px' }}>
57        <thead><tr style={{ borderBottom: '2px solid #eee' }}>
58          <th style={{ textAlign: 'left', padding: '8px' }}>Product</th>
59          <th style={{ textAlign: 'right', padding: '8px' }}>Price</th>
60        </tr></thead>
61        <tbody>
62          <tr style={{ borderBottom: '1px solid #eee' }}><td style={{ padding: '8px' }}>Quantum Shield Pro</td><td style={{ textAlign: 'right', padding: '8px' }}>49.99 CR</td></tr>
63          <tr style={{ borderBottom: '1px solid #eee' }}><td style={{ padding: '8px' }}>Cyber Toolkit</td><td style={{ textAlign: 'right', padding: '8px' }}>24.99 CR</td></tr>
64          <tr><td style={{ padding: '8px', fontWeight: 'bold' }}>Total</td><td style={{ textAlign: 'right', padding: '8px', fontWeight: 'bold' }}>74.98 CR</td></tr>
65        </tbody>
66      </table>
67    </div>
68  </div>
69);
70
71const ResendDemo = () => {
72  const [selectedTemplate, setSelectedTemplate] = useState<string>('welcome');
73  const [sending, setSending] = useState(false);
74  const [logs, setLogs] = useState<string[]>([]);
75
76  const templates: EmailTemplate[] = [
77    { id: 'welcome', name: 'Welcome Email', subject: 'Welcome to Quantum Metropolis!', preview: <WelcomeEmailPreview /> },
78    { id: 'reset', name: 'Password Reset', subject: 'Password Reset', preview: <ResetPasswordPreview /> },
79    { id: 'invoice', name: 'Order Confirmation', subject: 'Order Confirmation #QM-2025-0042', preview: <InvoicePreview /> },
80  ];
81
82  const currentTemplate = templates.find(t => t.id === selectedTemplate)!;
83
84  const handleSend = async () => {
85    setSending(true);
86    setLogs([]);
87    const addLog = (msg: string) => setLogs(prev => [...prev, msg]);
88    addLog('POST /api/send - Sending email via Resend...');
89    await new Promise(r => setTimeout(r, 400));
90    addLog('resend.emails.send({ from: "noreply@quantum.city", to: "user@example.com" })');
91    await new Promise(r => setTimeout(r, 500));
92    addLog('React Email template rendered to HTML');
93    await new Promise(r => setTimeout(r, 300));
94    addLog('Response: { id: "re_abc123", statusCode: 200 }');
95    addLog('Email delivered successfully!');
96    setSending(false);
97  };
98
99  return (
100    <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#fff', fontFamily: 'sans-serif' }}>
101      <h1 style={{ color: '#64ffda', textAlign: 'center' }}>Next.js + Resend + React Email</h1>
102      <div style={{ maxWidth: '800px', margin: '20px auto' }}>
103        <div style={{ display: 'flex', gap: '8px', marginBottom: '16px', justifyContent: 'center' }}>
104          {templates.map(t => (
105            <button key={t.id} onClick={() => { setSelectedTemplate(t.id); setLogs([]); }}
106              style={{ padding: '8px 16px', background: selectedTemplate === t.id ? '#7c4dff' : 'rgba(255,255,255,0.1)', border: 'none', borderRadius: '6px', color: '#fff', cursor: 'pointer' }}>
107              {t.name}
108            </button>
109          ))}
110        </div>
111
112        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
113          <div>
114            <h3 style={{ color: '#7c4dff', marginBottom: '10px' }}>Email Preview</h3>
115            <div style={{ borderRadius: '10px', overflow: 'hidden', border: '1px solid #333' }}>
116              {currentTemplate.preview}
117            </div>
118          </div>
119          <div>
120            <h3 style={{ color: '#7c4dff', marginBottom: '10px' }}>API Console</h3>
121            <div style={{ background: 'rgba(255,255,255,0.05)', padding: '16px', borderRadius: '10px', border: '1px solid #333' }}>
122              <p style={{ color: '#888', fontSize: '13px', marginBottom: '8px' }}>Subject: {currentTemplate.subject}</p>
123              <button onClick={handleSend} disabled={sending}
124                style={{ width: '100%', padding: '10px', background: sending ? '#555' : '#64ffda', border: 'none', borderRadius: '6px', color: '#0f0f23', fontWeight: 'bold', cursor: sending ? 'default' : 'pointer' }}>
125                {sending ? 'Sending...' : 'Send email'}
126              </button>
127            </div>
128            {logs.length > 0 && (
129              <div style={{ background: '#000', padding: '12px', borderRadius: '8px', marginTop: '10px', fontFamily: 'monospace', fontSize: '11px' }}>
130                {logs.map((log, i) => (<div key={i} style={{ color: '#0f0', padding: '2px 0' }}>{log}</div>))}
131              </div>
132            )}
133          </div>
134        </div>
135      </div>
136    </div>
137  );
138};
139
140export default ResendDemo;

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. React Email allows you to:

  2. 2. Which set of components is NOT part of the React Email library?

Hands-on tasks in the game

  • Click in order

    Arrange the steps of integrating Resend with Next.js in order.

  • Code editor

    Create an email component using React Email with an order confirmation template

  • Code editor

    Build a welcome email with React Email components.

Useful articles