Integração Resend Email
O Resend é o serviço de email transacional usado pelo Tarefa AI para enviar notificações, relatórios e alertas automaticamente quando suas tarefas são executadas.
Visão Geral
A integração com Resend permite:
- Enviar notificações quando tarefas são concluídas
- Enviar relatórios por email em horários agendados
- Alertar sobre erros ou falhas
- Enviar resultados de processamento de IA
- Emails transacionais (boas-vindas, redefinição de senha)
Pré-requisitos
- Conta no Resend
- Domínio próprio verificado (ou usar domínio de teste)
- Variáveis de ambiente configuradas
Configuração Passo a Passo
1. Criar Conta no Resend
- Acesse resend.com
- Clique em Get Started ou Sign Up
- Faça login com GitHub, Google ou email
- Confirme seu email de cadastro
- Complete o onboarding inicial
Plano Gratuito: 100 emails/dia, 3.000 emails/mês - suficiente para começar!
2. Obter API Key
- No dashboard, clique em API Keys no menu lateral
- Clique em Create API Key
- Dê um nome descritivo (ex: "Tarefa AI Production")
- Selecione as permissões:
- ✅ Sending access (obrigatório)
- ⬜ Full access (não recomendado para produção)
- Clique em Add e copie a chave
Segurança: A chave começa com re_ e só será exibida uma vez. Salve-a imediatamente!
3. Configurar Domínio
Opção A: Usar Domínio de Teste (Desenvolvimento)
O Resend fornece onboarding@resend.dev para testes:
# .env.local
RESEND_API_KEY=re_sua_chave_aqui
RESEND_FROM_EMAIL=onboarding@resend.devLimitação: O domínio de teste só envia emails para o email da conta Resend.
Opção B: Configurar Domínio Próprio (Produção)
- No dashboard, clique em Domains
- Clique em Add Domain
- Digite seu domínio (ex:
tarefaai.com) - Adicione os registros DNS fornecidos:
# Adicione estes registros no seu provedor DNS (Cloudflare, Vercel, etc)
# SPF (obrigatório)
Type: TXT
Name: @
Value: v=spf1 include:_spf.resend.com ~all
# DKIM (obrigatório)
Type: TXT
Name: resend._domainkey
Value: [valor fornecido pelo Resend]
# DMARC (recomendado)
Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none; rua=mailto:dmarc@seudominio.com- Aguarde verificação (5-30 minutos)
- Status mudará para ✅ Verified
# Acesse: cloudflare.com → DNS → Records
# Adicione os 3 registros TXT acima# Acesse: vercel.com → Settings → Domains → DNS
# Adicione os registros manualmente# Advanced DNS → Add New Record
# Tipo: TXT Record4. Configurar Variáveis de Ambiente
Desenvolvimento Local (.env.local)
# Resend Configuration
RESEND_API_KEY=re_sua_chave_aqui
RESEND_FROM_EMAIL=noreply@seudominio.com
RESEND_FROM_NAME=Tarefa AI
# Optional: Reply-to
RESEND_REPLY_TO=contato@seudominio.comProdução (Vercel/Railway/Render)
Vercel:
# Via CLI
vercel env add RESEND_API_KEY
vercel env add RESEND_FROM_EMAIL
# Ou no dashboard
# Settings → Environment Variables
# Adicione as variáveis e faça redeployRailway:
railway variables set RESEND_API_KEY=re_sua_chave
railway variables set RESEND_FROM_EMAIL=noreply@seudominio.comDocker:
# docker-compose.yml
services:
app:
environment:
- RESEND_API_KEY=${RESEND_API_KEY}
- RESEND_FROM_EMAIL=${RESEND_FROM_EMAIL}5. Verificar Instalação
Envie um email de teste:
// scripts/test-resend.ts
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
async function testEmail() {
try {
const result = await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: 'seu-email@example.com',
subject: 'Teste de Integração Resend',
html: '<h1>Funcionou!</h1><p>O Resend está configurado corretamente.</p>',
});
console.log('Email enviado com sucesso!', result);
} catch (error) {
console.error('Erro ao enviar email:', error);
}
}
testEmail();Execute:
npm run test:resend
# ou
npx tsx scripts/test-resend.tsExemplos Práticos
Exemplo 1: Notificação de Tarefa Concluída
// src/lib/email/task-completed.tsx
import { Resend } from 'resend';
import { TaskCompletedEmail } from '@/emails/task-completed';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendTaskCompletedEmail({
taskName,
result,
userEmail,
}: {
taskName: string;
result: any;
userEmail: string;
}) {
try {
const { data, error } = await resend.emails.send({
from: `${process.env.RESEND_FROM_NAME} <${process.env.RESEND_FROM_EMAIL}>`,
to: userEmail,
subject: `Tarefa "${taskName}" concluída com sucesso`,
react: TaskCompletedEmail({ taskName, result }),
});
if (error) {
console.error('Erro ao enviar email:', error);
return { success: false, error };
}
console.log('Email enviado:', data?.id);
return { success: true, emailId: data?.id };
} catch (error) {
console.error('Exceção ao enviar email:', error);
return { success: false, error };
}
}Exemplo 2: Template React Email
// emails/task-completed.tsx
import {
Body,
Container,
Head,
Heading,
Html,
Link,
Preview,
Section,
Text,
Button,
} from '@react-email/components';
interface TaskCompletedEmailProps {
taskName: string;
result: {
output?: string;
executedAt: string;
duration: number;
};
}
export function TaskCompletedEmail({ taskName, result }: TaskCompletedEmailProps) {
return (
<Html>
<Head />
<Preview>Sua tarefa "{taskName}" foi concluída com sucesso</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Tarefa Concluída ✅</Heading>
<Text style={text}>
Sua tarefa <strong>{taskName}</strong> foi executada com sucesso!
</Text>
<Section style={resultBox}>
<Text style={resultLabel}>Resultado:</Text>
<Text style={resultText}>{result.output || 'Sem output'}</Text>
</Section>
<Section style={detailsBox}>
<Text style={detailLine}>
<strong>Executado em:</strong> {new Date(result.executedAt).toLocaleString('pt-BR')}
</Text>
<Text style={detailLine}>
<strong>Duração:</strong> {result.duration}ms
</Text>
</Section>
<Button
href={`https://tarefaai.com/dashboard/tasks/${taskName}`}
style={button}
>
Ver Detalhes
</Button>
<Text style={footer}>
Tarefa AI - Automação Inteligente<br />
<Link href="https://tarefaai.com">tarefaai.com</Link>
</Text>
</Container>
</Body>
</Html>
);
}
// Estilos
const main = { backgroundColor: '#f6f9fc', fontFamily: 'sans-serif' };
const container = { backgroundColor: '#ffffff', margin: '0 auto', padding: '20px 0 48px', marginBottom: '64px' };
const h1 = { color: '#333', fontSize: '24px', fontWeight: 'bold', margin: '40px 0', padding: '0', textAlign: 'center' as const };
const text = { color: '#333', fontSize: '16px', lineHeight: '26px', margin: '16px 0' };
const resultBox = { backgroundColor: '#f4f4f5', borderRadius: '5px', padding: '24px', margin: '24px 0' };
const resultLabel = { color: '#71717a', fontSize: '14px', fontWeight: 'bold', margin: '0 0 8px 0' };
const resultText = { color: '#18181b', fontSize: '14px', margin: '0', whiteSpace: 'pre-wrap' as const };
const detailsBox = { margin: '24px 0' };
const detailLine = { color: '#52525b', fontSize: '14px', margin: '8px 0' };
const button = { backgroundColor: '#2563eb', borderRadius: '5px', color: '#fff', fontSize: '16px', fontWeight: 'bold', textDecoration: 'none', textAlign: 'center' as const, display: 'block', padding: '12px', margin: '24px 0' };
const footer = { color: '#8898aa', fontSize: '12px', lineHeight: '16px', textAlign: 'center' as const, margin: '32px 0' };Exemplo 3: Relatório Diário Agendado
// src/lib/tasks/daily-report.ts
import { generateText } from 'ai';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { sendDailyReportEmail } from '@/lib/email/daily-report';
export async function sendDailyReport() {
// 1. Coletar dados do dia
const todayTasks = await prisma.taskExecution.findMany({
where: {
executedAt: {
gte: new Date(new Date().setHours(0, 0, 0, 0)),
},
},
include: {
task: true,
},
});
// 2. Gerar insights com IA
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const insights = await generateText({
model: openrouter('anthropic/claude-3.5-sonnet'),
prompt: `
Analise as seguintes execuções de tarefas e gere insights:
${JSON.stringify(todayTasks, null, 2)}
Forneça:
1. Resumo do dia
2. Tarefas mais executadas
3. Taxa de sucesso
4. Recomendações
`,
});
// 3. Enviar email para todos os usuários
const users = await prisma.user.findMany({
where: {
emailNotifications: true,
},
});
const results = await Promise.all(
users.map((user) =>
sendDailyReportEmail({
userEmail: user.email!,
userName: user.name || 'Usuário',
stats: {
totalTasks: todayTasks.length,
successRate: calculateSuccessRate(todayTasks),
topTasks: getTopTasks(todayTasks),
},
insights: insights.text,
})
)
);
console.log(`Relatórios enviados: ${results.filter((r) => r.success).length}/${results.length}`);
}Exemplo 4: Email de Erro com Stack Trace
// src/lib/email/error-notification.ts
export async function sendErrorNotification({
taskName,
error,
adminEmail,
}: {
taskName: string;
error: Error;
adminEmail: string;
}) {
await resend.emails.send({
from: `Alertas ${process.env.RESEND_FROM_EMAIL}`,
to: adminEmail,
subject: `Erro na tarefa: ${taskName}`,
html: `
<h2>Erro Detectado</h2>
<p><strong>Tarefa:</strong> ${taskName}</p>
<p><strong>Erro:</strong> ${error.message}</p>
<h3>Stack Trace:</h3>
<pre style="background: #f4f4f5; padding: 16px; border-radius: 8px; overflow-x: auto;">
${error.stack}
</pre>
<p><strong>Timestamp:</strong> ${new Date().toISOString()}</p>
`,
});
}Exemplo 5: Email com Anexo
// src/lib/email/send-with-attachment.ts
export async function sendReportWithAttachment({
userEmail,
reportData,
}: {
userEmail: string;
reportData: any;
}) {
// Gerar CSV
const csv = generateCSV(reportData);
const buffer = Buffer.from(csv);
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL!,
to: userEmail,
subject: 'Seu relatório mensal',
html: '<p>Segue em anexo seu relatório mensal.</p>',
attachments: [
{
filename: `relatorio-${new Date().toISOString().split('T')[0]}.csv`,
content: buffer,
},
],
});
}Templates Prontos
Template de Boas-vindas
// emails/welcome.tsx
export function WelcomeEmail({ userName }: { userName: string }) {
return (
<Html>
<Head />
<Preview>Bem-vindo ao Tarefa AI!</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Bem-vindo, {userName}! 🎉</Heading>
<Text style={text}>
Obrigado por se juntar ao Tarefa AI. Estamos animados para ter você conosco!
</Text>
<Section style={featuresBox}>
<Heading style={h2}>O que você pode fazer:</Heading>
<ul>
<li>Agendar tarefas com IA</li>
<li>Automatizar workflows</li>
<li>Receber notificações por email e WhatsApp</li>
<li>Integrar com Notion e outras ferramentas</li>
</ul>
</Section>
<Button href="https://tarefaai.com/dashboard" style={button}>
Começar Agora
</Button>
<Text style={footer}>
Precisa de ajuda? <Link href="https://tarefaai.com/docs">Consulte nossa documentação</Link>
</Text>
</Container>
</Body>
</Html>
);
}Template de Redefinição de Senha
// emails/password-reset.tsx
export function PasswordResetEmail({ resetLink }: { resetLink: string }) {
return (
<Html>
<Head />
<Preview>Redefina sua senha - Tarefa AI</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Redefinir Senha</Heading>
<Text style={text}>
Você solicitou a redefinição de senha. Clique no botão abaixo para criar uma nova senha:
</Text>
<Button href={resetLink} style={button}>
Redefinir Senha
</Button>
<Text style={text}>
Este link expira em 1 hora.
</Text>
<Text style={footer}>
Se você não solicitou isso, ignore este email.
</Text>
</Container>
</Body>
</Html>
);
}Otimização e Boas Práticas
1. Rate Limiting
// src/lib/email/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '1 h'), // 10 emails por hora por usuário
});
export async function sendEmailWithRateLimit(userId: string, emailData: any) {
const { success } = await ratelimit.limit(userId);
if (!success) {
throw new Error('Limite de emails excedido. Aguarde 1 hora.');
}
return resend.emails.send(emailData);
}2. Retry com Exponential Backoff
// src/lib/email/retry.ts
export async function sendEmailWithRetry(
emailData: any,
maxRetries = 3
) {
for (let i = 0; i < maxRetries; i++) {
try {
const result = await resend.emails.send(emailData);
return result;
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Tentativa ${i + 1} falhou. Aguardando ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}3. Batch Sending
// src/lib/email/batch.ts
export async function sendBatchEmails(emails: Array<any>) {
const BATCH_SIZE = 100; // Resend recomenda max 100 por batch
for (let i = 0; i < emails.length; i += BATCH_SIZE) {
const batch = emails.slice(i, i + BATCH_SIZE);
await resend.batch.send(batch);
// Delay entre batches
if (i + BATCH_SIZE < emails.length) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
}4. Tracking de Emails
// src/lib/email/tracking.ts
export async function sendEmailWithTracking(emailData: any) {
const result = await resend.emails.send({
...emailData,
tags: [
{ name: 'category', value: 'transactional' },
{ name: 'environment', value: process.env.NODE_ENV },
],
});
// Salvar no banco para tracking
await prisma.emailLog.create({
data: {
emailId: result.data?.id,
to: emailData.to,
subject: emailData.subject,
status: 'sent',
sentAt: new Date(),
},
});
return result;
}Troubleshooting
Erro: "API key is invalid"
Causa: Chave API incorreta ou não configurada
Solução:
# Verificar variável de ambiente
echo $RESEND_API_KEY
# Deve começar com re_
# Se não estiver definida:
# Adicione ao .env.local ou variáveis do deployErro: "Domain not verified"
Causa: Registros DNS não configurados ou ainda não propagados
Solução:
- Verifique os registros DNS no seu provedor
- Use ferramenta de verificação: mxtoolbox.com/SuperTool.aspx
- Aguarde até 48h para propagação DNS
- Para desenvolvimento, use
onboarding@resend.dev
Emails Não Chegam
Possíveis causas e soluções:
-
Na pasta de spam:
- Configure SPF, DKIM e DMARC corretamente
- Use domínio verificado
- Evite palavras de spam no assunto
-
Email inválido:
// Validar email antes de enviar
import { z } from 'zod';
const emailSchema = z.string().email();
if (!emailSchema.safeParse(userEmail).success) {
throw new Error('Email inválido');
}- Limite de envio atingido:
// Verificar limites no dashboard Resend
// Plano gratuito: 100/dia, 3.000/mêsErro: "Rate limit exceeded"
Causa: Muitos emails enviados rapidamente
Solução:
// Implementar delay entre envios
async function sendEmailsWithDelay(emails: any[]) {
for (const email of emails) {
await resend.emails.send(email);
await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms delay
}
}Templates não Renderizam
Causa: Componente React Email incorreto
Solução:
# Instalar dependências
npm install @react-email/components
# Testar template localmente
npm run email:dev// package.json
{
"scripts": {
"email:dev": "email dev"
}
}Monitoramento e Analytics
Dashboard do Resend
Acesse resend.com/emails para ver:
- Emails enviados
- Taxa de entrega
- Opens e clicks (se configurado)
- Bounces e reclamações
Webhooks para Eventos
Configure webhooks para receber eventos:
// src/app/api/webhooks/resend/route.ts
export async function POST(request: Request) {
const event = await request.json();
switch (event.type) {
case 'email.delivered':
await prisma.emailLog.update({
where: { emailId: event.data.email_id },
data: { status: 'delivered', deliveredAt: new Date() },
});
break;
case 'email.bounced':
await prisma.emailLog.update({
where: { emailId: event.data.email_id },
data: { status: 'bounced', bouncedAt: new Date() },
});
break;
case 'email.complained':
// Marcar usuário para não enviar mais emails
await prisma.user.update({
where: { email: event.data.recipient },
data: { emailNotifications: false },
});
break;
}
return new Response('OK', { status: 200 });
}Configure o webhook no Resend:
- Acesse Settings → Webhooks
- Adicione:
https://seusite.com/api/webhooks/resend - Selecione eventos desejados
Screenshot Placeholder
[INSERIR SCREENSHOT: Dashboard do Resend mostrando estatísticas de emails enviados]
Recursos Adicionais
Documentação Oficial
Comunidade
Suporte
- Email: support@resend.com
- Response time: 24-48h
Próximos Passos
Agora que você configurou o Resend, explore:
- Integração com WhatsApp - Notificações em tempo real
- Integração com Notion - Salve logs em banco de dados
- Webhooks API - Trigger tarefas via HTTP
Precisa de ajuda?
Consulte nosso guia completo de troubleshooting