Serverless-Funktionen haben besondere Einschränkungen beim E-Mail-Versand: Cold Starts, Timeouts und Zustandslosigkeit. So sendest du E-Mails effektiv aus Lambda-, Edge-Funktionen und ähnlichen Umgebungen.
Serverless-Einschränkungen
- —Latenz bei Cold Starts
- —Begrenzte Ausführungszeit (oft 10–30 Sekunden)
- —Keine persistenten Verbindungen
- —Zustandslose Ausführung
- —Abrechnung pro Aufruf
Muster für direktes Senden
Einfache E-Mail mit Lambda
import { Emailr } from 'emailr';
const emailr = new Emailr(process.env.EMAILR_API_KEY);
export async function handler(event: APIGatewayEvent) {
const { to, subject, html } = JSON.parse(event.body);
try {
const result = await emailr.emails.send({
from: '[email protected]',
to,
subject,
html
});
return {
statusCode: 200,
body: JSON.stringify({ id: result.id })
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed to send email' })
};
}
}
Umgang mit Cold Starts
// Initialize outside handler for connection reuse
const emailr = new Emailr(process.env.EMAILR_API_KEY);
// Warm connections with provisioned concurrency
export const handler = async (event) => {
// Handler code - connection already warm
};
Warteschlangenbasiertes Muster
Bei hohem Volumen oder nicht-kritischen E-Mails: in eine Queue stellen statt direkt zu senden:
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
const sqs = new SQSClient({});
export async function queueEmail(event: APIGatewayEvent) {
const email = JSON.parse(event.body);
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.EMAIL_QUEUE_URL,
MessageBody: JSON.stringify(email),
MessageAttributes: {
priority: {
DataType: 'String',
StringValue: email.priority || 'normal'
}
}
}));
return { statusCode: 202, body: JSON.stringify({ queued: true }) };
}
// Separate worker Lambda processes queue
export async function processEmailQueue(event: SQSEvent) {
const results = await Promise.allSettled(
event.Records.map(record => {
const email = JSON.parse(record.body);
return emailr.emails.send(email);
})
);
// Failed messages return to queue automatically
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
throw new Error(`${failures.length} emails failed`);
}
}
Edge-Funktionsmuster
Cloudflare Workers
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { to, subject, html } = await request.json();
const response = await fetch('https://api.emailr.dev/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.EMAILR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ from: '[email protected]', to, subject, html })
});
return new Response(JSON.stringify({ sent: response.ok }), {
headers: { 'Content-Type': 'application/json' }
});
}
};
Vercel Edge Functions
import { NextResponse } from 'next/server';
export const runtime = 'edge';
export async function POST(request: Request) {
const { to, subject, html } = await request.json();
const response = await fetch('https://api.emailr.dev/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.EMAILR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ from: '[email protected]', to, subject, html })
});
return NextResponse.json({ sent: response.ok });
}
Umgang mit Timeouts
// Set aggressive timeouts for serverless
const emailr = new Emailr(process.env.EMAILR_API_KEY, {
timeout: 5000 // 5 seconds max
});
export async function handler(event) {
try {
const result = await Promise.race([
emailr.emails.send(email),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 8000)
)
]);
return { statusCode: 200, body: JSON.stringify(result) };
} catch (error) {
if (error.message === 'Timeout') {
// Queue for retry instead of failing
await queueForRetry(email);
return { statusCode: 202, body: JSON.stringify({ queued: true }) };
}
throw error;
}
}
Batch-Verarbeitung
// Process multiple emails efficiently
export async function batchHandler(event: SQSEvent) {
const emails = event.Records.map(r => JSON.parse(r.body));
// Batch API call if supported
const result = await emailr.emails.sendBatch(emails);
return {
batchItemFailures: result.failed.map(f => ({
itemIdentifier: f.messageId
}))
};
}
Best Practices
- —Initialisierung außerhalb des Handlers – Verbindungen wiederverwenden
- —Queues für Volumen nutzen – Sendevorgänge nicht blockieren
- —Timeouts setzen – Funktionslimits nicht überschreiten
- —Fehler robust behandeln – für erneuten Versuch in die Queue stellen
- —Cold Starts beobachten – bei Bedarf Provisioned Concurrency verwenden
- —Payloads klein halten – Datenübertragung minimieren
Serverless-E-Mail bedeutet, innerhalb von Einschränkungen zu arbeiten. Wenn du für das Ausführungsmodell entwirfst, erreichst du zuverlässige, skalierbare E-Mail.