Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

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

W Metropolii Quantum, gdzie komunikacja odbywa się z prędkością światła, poznasz połączenie Next.js z Resend i React Email - najnowocześniejszym stackiem do wysyłania emaili. Ta kombinacja pozwala tworzyć piękne, responsywne emaile i wysyłać je z niezawodnością enterprise-grade.

Czym jest Resend?

Resend to nowoczesna platforma do wysyłania emaili, stworzona przez zespół deweloperów dla deweloperów:

  • Developer Experience - czyste API i świetna dokumentacja
  • React Email integration - natywne wsparcie dla komponentów React
  • High deliverability - zaawansowane mechanizmy dostarczalności
  • Analytics - szczegółowe statystyki dostarczania
  • Webhooks - real-time powiadomienia o statusie emaili
  • Templates - system szablonów z React

Czym jest React Email?

React Email to biblioteka do tworzenia emaili przy użyciu React:

  • Component-based - tworzenie emaili jak zwykłych komponentów React
  • TypeScript support - pełne wsparcie typowania
  • Preview - podgląd emaili w przeglądarce
  • Cross-client compatibility - kompatybilność z wszystkimi klientami email
  • Export to HTML - konwersja do statycznego HTML

Instalacja i konfiguracja

Krok 1: Utworzenie konta Resend

  1. Zarejestruj się na resend.com
  2. Zweryfikuj domenę lub użyj domeny testowej
  3. Utwórz API key w Dashboard → API Keys

Krok 2: Instalacja pakietów

1# Zainstaluj Resend i React Email
2npm install resend react-email @react-email/components
3
4# Dla development dodaj React Email CLI
5npm install -D @react-email/cli

Krok 3: Konfiguracja zmiennych środowiskowych

Utwórz plik

.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

Krok 4: Setup React Email

Dodaj skrypty do

package.json
:

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

Utwórz konfigurację

react-email.config.js
:

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

Podstawowa integracja

Krok 1: Inicjalizacja Resend

Utwórz instancję Resend:

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);

Krok 2: Struktura folderów

Utwórz strukturę dla emaili:

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

Tworzenie komponentów emaili

Komponenty podstawowe

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}. Wszystkie prawa zastrzeżone.
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            Wypisz się z newslettera
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};

Szablony emaili

Email powitalny

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              Witaj, {userFirstName}! 🎉
37            </Heading>
38            
39            <Text style={textStyle}>
40              Dziękujemy za rejestrację w {companyName}! Jesteśmy podekscytowani, 
41              że dołączyłeś do naszej społeczności.
42            </Text>
43            
44            <Text style={textStyle}>
45              Aby rozpocząć korzystanie z naszej platformy, kliknij poniższy przycisk:
46            </Text>
47            
48            <Section style={buttonSectionStyle}>
49              <Button href={loginUrl}>
50                Zaloguj się do konta
51              </Button>
52            </Section>
53            
54            <Hr style={hrStyle} />
55            
56            <Text style={helpTextStyle}>
57              Jeśli masz pytania, nie wahaj się <a href="mailto:support@example.com" style={linkStyle}>skontaktować z nami</a>.
58            </Text>
59          </Section>
60          
61          <Footer 
62            companyName={companyName}
63            address="ul. Przykładowa 123, 00-000 Warszawa"
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};

Email resetowania hasła

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              Resetowanie hasła 🔒
41            </Heading>
42            
43            <Text style={textStyle}>
44              Cześć {userFirstName},
45            </Text>
46            
47            <Text style={textStyle}>
48              Otrzymaliśmy prośbę o zresetowanie hasła do Twojego konta w {companyName}.
49              Jeśli to nie Ty, zignoruj tego emaila.
50            </Text>
51            
52            <Section style={buttonSectionStyle}>
53              <Button href={resetUrl}>
54                Zresetuj hasło
55              </Button>
56            </Section>
57            
58            {resetCode && (
59              <Section style={codeSectionStyle}>
60                <Text style={codeTextStyle}>
61                  Lub użyj tego kodu:
62                </Text>
63                <Code style={codeStyle}>{resetCode}</Code>
64              </Section>
65            )}
66            
67            <Text style={warningTextStyle}>
68              ⚠️ Ten link wygaśnie za {expirationTime}.
69            </Text>
70            
71            <Text style={securityTextStyle}>
72              Ze względów bezpieczeństwa, jeśli nie prosiłeś o reset hasła, 
73              <a href="mailto:security@example.com" style={linkStyle}> skontaktuj się z nami natychmiast</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 dla wysyłania emaili

Podstawowe 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    // Walidacja podstawowa
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    // Wybór szablonu na podstawie typu
23    switch (type) {
24      case 'welcome':
25        emailComponent = WelcomeEmail({
26          userFirstName: data.userFirstName,
27          loginUrl: data.loginUrl,
28          companyName: data.companyName || 'Nasza Firma',
29        });
30        subject = `Witaj w ${data.companyName || 'Naszej Firmie'}!`;
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 || 'Nasza Firma',
39          expirationTime: data.expirationTime || '24 godziny',
40        });
41        subject = 'Resetowanie hasła';
42        break;
43
44      default:
45        return NextResponse.json(
46          { error: `Unknown email type: ${type}` },
47          { status: 400 }
48        );
49    }
50
51    // Wysłanie emaila
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}

Wysyłanie email z załącznikami

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    // Dodanie załącznika jeśli istnieje
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 || 'Nasza Firma',
54    });
55
56    return this.sendEmail(component, {
57      ...options,
58      subject: options.subject || `Witaj w ${data.companyName || 'Naszej Firmie'}!`,
59    });
60  }
61
62  static async sendPasswordResetEmail(
63    data: PasswordResetEmailData,
64    options: EmailOptions
65  ) {
66    const component = PasswordResetEmail({
67      ...data,
68      companyName: data.companyName || 'Nasza Firma',
69      expirationTime: data.expirationTime || '24 godziny',
70    });
71
72    return this.sendEmail(component, {
73      ...options,
74      subject: options.subject || 'Resetowanie hasła',
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 dla integracji z komponentami

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}

Przykład użycia w komponencie

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('Email powitalny został wysłany!');
27    } catch (error) {
28      alert('Błąd podczas wysyłania emaila');
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 ? 'Wysyłanie...' : 'Wyślij email powitalny'}
40      </button>
41      
42      {error && (
43        <p className="text-red-600 text-sm mt-2">{error}</p>
44      )}
45    </div>
46  );
47}

Webhooks i analityka

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    // Logowanie eventów
10    console.log(`Resend webhook: ${type}`, data);
11
12    switch (type) {
13      case 'email.sent':
14        // Email został wysłany
15        await handleEmailSent(data);
16        break;
17
18      case 'email.delivered':
19        // Email został dostarczony
20        await handleEmailDelivered(data);
21        break;
22
23      case 'email.bounced':
24        // Email został odrzucony
25        await handleEmailBounced(data);
26        break;
27
28      case 'email.complained':
29        // Email został zgłoszony jako 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  // Implementacja logiki po wysłaniu emaila
49  console.log('Email sent:', data.email_id);
50}
51
52async function handleEmailDelivered(data: any) {
53  // Implementacja logiki po dostarczeniu emaila
54  console.log('Email delivered:', data.email_id);
55}
56
57async function handleEmailBounced(data: any) {
58  // Implementacja logiki po odrzuceniu emaila
59  console.log('Email bounced:', data.email_id, data.reason);
60  
61  // Możesz zaktualizować status użytkownika w bazie danych
62  // await updateUserEmailStatus(data.to, 'bounced');
63}
64
65async function handleEmailComplained(data: any) {
66  // Implementacja logiki po zgłoszeniu spamu
67  console.log('Email complained:', data.email_id);
68  
69  // Automatyczne wypisanie z newslettera
70  // await unsubscribeUser(data.to);
71}

Development i testing

Preview emaili

Uruchom serwer preview:

1npm run email:dev

Przejdź do

http://localhost:3000
aby zobaczyć podgląd wszystkich emaili.

Export do HTML

1npm run email:export

Testowanie w różnych klientach

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  // Zapisz HTML do pliku dla testów
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();

Najlepsze praktyki

1. Personalizacja

1// Zawsze używaj danych użytkownika
2const personalizedSubject = `Cześć ${firstName}, sprawdź swoje nowe funkcje!`;

2. Responsive design

1// Używaj responsywnych stylów
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// Testuj różne wersje emaili
2const subjectVariants = [
3  'Witaj w naszej aplikacji!',
4  'Twoje konto zostało utworzone',
5  'Zaczynajmy przygodę razem!',
6];
7
8const randomSubject = subjectVariants[Math.floor(Math.random() * subjectVariants.length)];

4. Rate limiting

1// Ogranicz liczbę emaili na użytkownika
2const DAILY_EMAIL_LIMIT = 10;
3
4async function checkEmailLimit(userId: string) {
5  // Sprawdź ile emaili wysłano dzisiaj do tego użytkownika
6  // Implementacja zależna od bazy danych
7}

Podsumowanie

Połączenie Next.js z Resend i React Email tworzy potężny stack do wysyłania emaili:

  1. React Email - komponentowe tworzenie emaili
  2. Resend - niezawodne dostarczanie
  3. TypeScript - bezpieczeństwo typów
  4. Webhooks - real-time analytics
  5. Preview - łatwy development

Ta kombinacja pozwala budować profesjonalne systemy email marketingu, powiadomień transakcyjnych i komunikacji z użytkownikami.

Vai a CodeWorlds