Files
Portfolio/blog-posts/59-email-automation.md
damjan_savicandClaude Opus 4.5 43484c5023 Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:42:11 +01:00

594 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Email Automation mit Resend & React Email
**Meta-Description:** Moderne Email Automation. React Email Templates, Resend API, Transactional Emails und Email Sequenzen.
**Keywords:** Email Automation, Resend, React Email, Transactional Email, Email Templates, Nodemailer, SMTP
---
## Einführung
Email bleibt der wichtigste Kommunikationskanal. Mit **React Email** und **Resend** werden Email-Templates zu First-Class React Components testbar, typsicher und versioniert.
---
## Email Stack 2026
```
┌─────────────────────────────────────────────────────────────┐
│ MODERN EMAIL STACK │
├─────────────────────────────────────────────────────────────┤
│ │
│ Template Layer: │
│ ├── React Email (JSX → HTML) │
│ ├── MJML Alternative │
│ └── Tailwind Email Support │
│ │
│ Delivery Layer: │
│ ├── Resend (Developer-First) │
│ ├── SendGrid │
│ ├── Postmark │
│ └── AWS SES │
│ │
│ Features: │
│ ├── Transactional (Welcome, Reset, etc.) │
│ ├── Marketing (Newsletters) │
│ ├── Sequences (Drip Campaigns) │
│ └── Webhooks (Delivery Status) │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## React Email Setup
```bash
npm install @react-email/components react-email resend
```
```typescript
// emails/welcome.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Text,
Button,
Img,
Hr,
Link
} from '@react-email/components';
interface WelcomeEmailProps {
userName: string;
verificationUrl: string;
}
export default function WelcomeEmail({
userName,
verificationUrl
}: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Willkommen bei unserer Plattform, {userName}!</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://example.com/logo.png"
width="120"
height="40"
alt="Logo"
style={logo}
/>
<Section style={content}>
<Text style={heading}>Willkommen, {userName}! 👋</Text>
<Text style={paragraph}>
Vielen Dank für Ihre Registrierung. Bitte bestätigen Sie Ihre
E-Mail-Adresse, um alle Funktionen nutzen zu können.
</Text>
<Button style={button} href={verificationUrl}>
E-Mail bestätigen
</Button>
<Text style={paragraph}>
Oder kopieren Sie diesen Link in Ihren Browser:
</Text>
<Text style={link}>{verificationUrl}</Text>
</Section>
<Hr style={hr} />
<Section style={footer}>
<Text style={footerText}>
© 2026 Unser Unternehmen. Alle Rechte vorbehalten.
</Text>
<Link href="https://example.com/unsubscribe" style={footerLink}>
Abmelden
</Link>
</Section>
</Container>
</Body>
</Html>
);
}
// Styles
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0',
maxWidth: '600px'
};
const logo = {
margin: '0 auto 20px',
display: 'block'
};
const content = {
padding: '0 40px'
};
const heading = {
fontSize: '24px',
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: '20px'
};
const paragraph = {
fontSize: '16px',
lineHeight: '26px',
color: '#484848'
};
const button = {
backgroundColor: '#5046e5',
borderRadius: '8px',
color: '#ffffff',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'block',
padding: '14px 24px',
margin: '20px 0'
};
const link = {
color: '#5046e5',
fontSize: '14px',
wordBreak: 'break-all' as const
};
const hr = {
borderColor: '#e6ebf1',
margin: '20px 0'
};
const footer = {
padding: '0 40px'
};
const footerText = {
fontSize: '12px',
color: '#9ca299'
};
const footerLink = {
color: '#9ca299',
fontSize: '12px'
};
```
---
## Resend Integration
```typescript
// lib/email.ts
import { Resend } from 'resend';
import WelcomeEmail from '@/emails/welcome';
import PasswordResetEmail from '@/emails/password-reset';
import OrderConfirmationEmail from '@/emails/order-confirmation';
const resend = new Resend(process.env.RESEND_API_KEY);
// Single Email senden
export async function sendWelcomeEmail(
to: string,
userName: string,
verificationUrl: string
) {
const { data, error } = await resend.emails.send({
from: 'Unser Team <noreply@example.com>',
to,
subject: `Willkommen, ${userName}!`,
react: WelcomeEmail({ userName, verificationUrl })
});
if (error) {
console.error('Email send error:', error);
throw error;
}
return data;
}
// Password Reset
export async function sendPasswordResetEmail(
to: string,
resetUrl: string
) {
return resend.emails.send({
from: 'Sicherheit <security@example.com>',
to,
subject: 'Passwort zurücksetzen',
react: PasswordResetEmail({ resetUrl })
});
}
// Order Confirmation mit Anhang
export async function sendOrderConfirmation(
to: string,
order: Order,
invoicePdf: Buffer
) {
return resend.emails.send({
from: 'Shop <orders@example.com>',
to,
subject: `Bestellbestätigung #${order.id}`,
react: OrderConfirmationEmail({ order }),
attachments: [
{
filename: `Rechnung-${order.id}.pdf`,
content: invoicePdf
}
]
});
}
// Batch Send
export async function sendBulkEmails(
recipients: Array<{ email: string; data: any }>
) {
const emails = recipients.map(r => ({
from: 'Newsletter <news@example.com>',
to: r.email,
subject: 'Unser Newsletter',
react: NewsletterEmail(r.data)
}));
return resend.batch.send(emails);
}
```
---
## Email Sequenzen (Drip Campaigns)
```typescript
import { Queue, Worker } from 'bullmq';
import IORedis from 'ioredis';
const connection = new IORedis(process.env.REDIS_URL!);
const emailQueue = new Queue('email-sequences', { connection });
// Sequence Definition
interface EmailSequence {
id: string;
name: string;
steps: Array<{
delay: number; // Millisekunden
template: string;
subject: string;
}>;
}
const onboardingSequence: EmailSequence = {
id: 'onboarding',
name: 'Onboarding Sequence',
steps: [
{
delay: 0,
template: 'welcome',
subject: 'Willkommen bei uns!'
},
{
delay: 24 * 60 * 60 * 1000, // 1 Tag
template: 'getting-started',
subject: 'Erste Schritte'
},
{
delay: 3 * 24 * 60 * 60 * 1000, // 3 Tage
template: 'tips',
subject: '5 Tipps für den Einstieg'
},
{
delay: 7 * 24 * 60 * 60 * 1000, // 7 Tage
template: 'feedback',
subject: 'Wie gefällt es Ihnen?'
}
]
};
// Sequence starten
async function startEmailSequence(
userId: string,
email: string,
sequenceId: string,
userData: Record<string, any>
) {
const sequence = getSequenceById(sequenceId);
if (!sequence) throw new Error(`Sequence ${sequenceId} not found`);
// Sequence State speichern
await db.emailSequenceState.create({
data: {
userId,
sequenceId,
currentStep: 0,
status: 'active'
}
});
// Jobs für jeden Step einplanen
for (let i = 0; i < sequence.steps.length; i++) {
const step = sequence.steps[i];
await emailQueue.add(
'send-sequence-email',
{
userId,
email,
sequenceId,
stepIndex: i,
template: step.template,
subject: step.subject,
userData
},
{
delay: step.delay,
jobId: `${userId}-${sequenceId}-${i}`
}
);
}
}
// Worker für Sequence Emails
const worker = new Worker('email-sequences', async (job) => {
const { userId, email, sequenceId, stepIndex, template, subject, userData } = job.data;
// Prüfen ob Sequence noch aktiv
const state = await db.emailSequenceState.findFirst({
where: { userId, sequenceId }
});
if (state?.status !== 'active') {
console.log(`Sequence ${sequenceId} for user ${userId} is not active, skipping`);
return;
}
// Email senden
const EmailComponent = getEmailTemplate(template);
await resend.emails.send({
from: 'Unser Team <team@example.com>',
to: email,
subject,
react: EmailComponent(userData)
});
// State updaten
await db.emailSequenceState.update({
where: { id: state.id },
data: { currentStep: stepIndex + 1 }
});
console.log(`Sent sequence email: ${template} to ${email}`);
}, { connection });
// Sequence abbrechen
async function cancelEmailSequence(userId: string, sequenceId: string) {
// State updaten
await db.emailSequenceState.updateMany({
where: { userId, sequenceId },
data: { status: 'cancelled' }
});
// Pending Jobs entfernen
const jobs = await emailQueue.getJobs(['delayed', 'waiting']);
for (const job of jobs) {
if (job.data.userId === userId && job.data.sequenceId === sequenceId) {
await job.remove();
}
}
}
```
---
## Email Preview & Testing
```typescript
// email-preview.ts (Development)
import { render } from '@react-email/render';
import WelcomeEmail from '@/emails/welcome';
// HTML Preview generieren
const html = render(WelcomeEmail({
userName: 'Max Mustermann',
verificationUrl: 'https://example.com/verify?token=abc123'
}));
console.log(html);
// React Email Dev Server
// package.json
{
"scripts": {
"email:dev": "email dev --port 3001"
}
}
// Dann: npm run email:dev
// Browser: http://localhost:3001
```
```typescript
// Email Testing mit Jest
import { render } from '@react-email/render';
import WelcomeEmail from '@/emails/welcome';
describe('WelcomeEmail', () => {
it('renders with correct user name', () => {
const html = render(WelcomeEmail({
userName: 'Test User',
verificationUrl: 'https://example.com/verify'
}));
expect(html).toContain('Test User');
expect(html).toContain('Willkommen');
});
it('includes verification link', () => {
const html = render(WelcomeEmail({
userName: 'Test User',
verificationUrl: 'https://example.com/verify?token=123'
}));
expect(html).toContain('https://example.com/verify?token=123');
});
it('has valid HTML structure', () => {
const html = render(WelcomeEmail({
userName: 'Test User',
verificationUrl: 'https://example.com/verify'
}));
expect(html).toMatch(/<html/);
expect(html).toMatch(/<\/html>/);
});
});
```
---
## Webhook Handling (Delivery Status)
```typescript
// app/api/webhooks/resend/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Webhook } from 'svix';
const webhookSecret = process.env.RESEND_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const payload = await request.text();
const headers = {
'svix-id': request.headers.get('svix-id')!,
'svix-timestamp': request.headers.get('svix-timestamp')!,
'svix-signature': request.headers.get('svix-signature')!
};
const wh = new Webhook(webhookSecret);
let event: any;
try {
event = wh.verify(payload, headers);
} catch (err) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}
// Event Type handling
switch (event.type) {
case 'email.sent':
await handleEmailSent(event.data);
break;
case 'email.delivered':
await handleEmailDelivered(event.data);
break;
case 'email.opened':
await handleEmailOpened(event.data);
break;
case 'email.clicked':
await handleEmailClicked(event.data);
break;
case 'email.bounced':
await handleEmailBounced(event.data);
break;
case 'email.complained':
await handleEmailComplained(event.data);
break;
}
return NextResponse.json({ received: true });
}
async function handleEmailBounced(data: any) {
// Email-Adresse als ungültig markieren
await db.user.update({
where: { email: data.to },
data: { emailStatus: 'bounced' }
});
}
async function handleEmailComplained(data: any) {
// Spam-Beschwerde: User aus Listen entfernen
await db.user.update({
where: { email: data.to },
data: { marketingOptIn: false }
});
}
```
---
## Fazit
Moderne Email Automation braucht:
1. **React Email**: Component-basierte Templates
2. **Resend**: Developer-First API
3. **Sequences**: Automatisierte Drip Campaigns
4. **Webhooks**: Delivery Tracking
Email als Code testbar, typsicher, versioniert.
---
## Bildprompts
1. "Email template components assembling like Lego blocks, React Email concept"
2. "Email sequence timeline with automated sends, drip campaign visualization"
3. "Delivery status tracking with checkmarks, email analytics dashboard"
---
## Quellen
- [React Email Documentation](https://react.email/)
- [Resend Documentation](https://resend.com/docs)
- [Email Accessibility Guidelines](https://www.emailonacid.com/blog/article/email-development/email-accessibilty-in-2019/)
- [Can I Email](https://www.caniemail.com/)