Funções serverless têm restrições únicas para email: cold starts, timeouts e ausência de estado. Veja como enviar email de forma eficaz com Lambda, funções Edge e ambientes semelhantes.
Restrições de serverless
- —Latência de cold start
- —Limites de tempo de execução (geralmente 10-30 segundos)
- —Sem conexões persistentes
- —Execução sem estado
- —Preço por invocação
Padrão de envio direto
Email simples com 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' })
};
}
}
Lidando com 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
};
Padrão baseado em fila
Para alto volume ou emails não críticos, coloque em fila em vez de enviar diretamente:
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`);
}
}
Padrões para funções 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 });
}
Tratamento de timeout
// 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;
}
}
Processamento em lote
// 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
}))
};
}
Boas práticas
- —Inicialize fora do handler - Reutilize conexões
- —Use filas para volume - Não bloqueie envios
- —Defina timeouts - Não exceda os limites da função
- —Trate falhas com elegância - Coloque em fila para retry
- —Monitore cold starts - Use provisioned concurrency se necessário
- —Mantenha payloads pequenos - Minimize a transferência de dados
Email serverless é sobre trabalhar dentro das restrições. Projete para o modelo de execução e você terá email confiável e escalável.