Las funciones serverless tienen restricciones particulares para el correo: cold starts, timeouts y ausencia de estado. Aquí te mostramos cómo enviar correo de forma eficaz desde Lambda, funciones Edge y entornos similares.
Restricciones en serverless
- —Latencia por cold start
- —Límites de tiempo de ejecución (a menudo 10-30 segundos)
- —Sin conexiones persistentes
- —Ejecución sin estado
- —Precio por invocación
Patrón de envío directo
Correo simple con 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' })
};
}
}
Manejo de 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
};
Patrón basado en cola
Para volúmenes altos o correos no críticos, usa una cola en lugar de enviar directamente:
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`);
}
}
Patrones para funciones 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 });
}
Manejo de 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;
}
}
Procesamiento por lotes
// 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
}))
};
}
Mejores prácticas
- —Inicializa fuera del handler - Reutiliza conexiones
- —Usa colas para volumen - No bloquees en los envíos
- —Configura timeouts - No excedas los límites de la función
- —Maneja los fallos correctamente - Encola para reintento
- —Supervisa los cold starts - Usa provisioned concurrency si es necesario
- —Mantén los payloads pequeños - Minimiza la transferencia de datos
El correo serverless consiste en trabajar dentro de las restricciones. Diseña para el modelo de ejecución y tendrás correo confiable y escalable.