Les fonctions serverless ont des contraintes spécifiques pour l’email : cold starts, timeouts et absence d’état. Voici comment envoyer des emails efficacement depuis Lambda, des fonctions Edge et des environnements similaires.
Contraintes du serverless
- —Latence de cold start
- —Limites de temps d’exécution (souvent 10–30 secondes)
- —Pas de connexions persistantes
- —Exécution sans état
- —Tarification à l’invocation
Modèle d’envoi direct
Email simple avec 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' })
};
}
}
Gestion des 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
};
Modèle basé sur une file d’attente
Pour des volumes élevés ou des emails non critiques, mettez en file d’attente plutôt que d’envoyer directement :
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`);
}
}
Modèles pour fonctions Edge
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 });
}
Gestion des 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;
}
}
Traitement par lots
// 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
}))
};
}
Bonnes pratiques
- —Initialiser en dehors du handler - Réutiliser les connexions
- —Utiliser des files d’attente pour le volume - Ne pas bloquer sur les envois
- —Définir des timeouts - Ne pas dépasser les limites de la fonction
- —Gérer les échecs avec élégance - Mettre en file d’attente pour réessayer
- —Surveiller les cold starts - Utiliser la provisioned concurrency si nécessaire
- —Garder des payloads petits - Minimiser le transfert de données
L’email serverless consiste à travailler dans les contraintes. Concevez pour le modèle d’exécution et vous aurez un email fiable et scalable.