Vanessa LozzardoConstruindo um assistente de e-mail com IA usando Sendkit
Combine um LLM com a API do Sendkit pra construir um assistente que rascunha, envia e monitora e-mails em nome dos seus usuários.

Seu usuário digita "manda um e-mail pro John avisando que o standup vai pras 15h". Trinta segundos depois, uma mensagem bem escrita cai na caixa do John — rascunhada por um LLM, aprovada pelo usuário, enviada pela sua infraestrutura e monitorada de ponta a ponta. Esse é o padrão de assistente de e-mail com IA, e é surpreendentemente direto de construir.
Este tutorial passa pela arquitetura, pelo código e pelas pontas afiadas que você precisa lixar antes de levar isso pra produção. A gente vai usar a OpenAI para a camada do LLM e a API de e-mail do Sendkit para envio e monitoramento de entrega.
O que um assistente de e-mail com IA faz de verdade
Tire o hype e você tem uma pipeline de quatro etapas:
- Interpretar a intenção — O usuário diz algo como "manda um e-mail pro John sobre a reunião". O LLM extrai o destinatário, o tópico e o tom.
- Rascunhar — O LLM gera um assunto e corpo com base na intenção extraída mais qualquer contexto disponível (threads anteriores, dados de calendário, preferências do usuário).
- Aprovar — O rascunho é mostrado ao usuário. Ele pode editar, regenerar ou enviar.
- Enviar e monitorar — O e-mail aprovado sai pela API do Sendkit. Webhooks reportam entrega, bounces e aberturas de volta pro seu app.
Cada etapa importa. Pule a aprovação e você eventualmente vai mandar algo embaraçoso. Pule o monitoramento de entrega e você está voando às cegas.

Visão geral da arquitetura
Aqui está o fluxo desenhado:
User input → LLM (draft) → Preview UI → User approves → Sendkit API (send)
↓
Webhook events (delivered/bounced/opened)
↓
Feed status back to assistant contextO backend é um serviço Node.js. A chamada do LLM e o envio do e-mail são chamadas de API separadas — nunca combine num único passo. Você quer um portão humano entre "a IA escreveu algo" e "esse algo foi pra uma pessoa real".
Construindo a etapa de rascunho
A etapa de rascunho chama seu LLM com a intenção do usuário e retorna um output estruturado. Use function calling ou um schema JSON pra forçar o formato da resposta.
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const draftEmail = async (userIntent, recipientContext) => {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: `You are an email drafting assistant. Given user intent and context, produce a JSON object with "subject" (string) and "body" (string, plain text). Keep the tone professional but natural. Do not invent facts.`,
},
{
role: 'user',
content: `Intent: ${userIntent}\nRecipient context: ${JSON.stringify(recipientContext)}`,
},
],
response_format: { type: 'json_object' },
});
return JSON.parse(response.choices[0].message.content);
};
// Usage
const draft = await draftEmail('Tell John the standup is moving to 3 PM tomorrow', {
name: 'John Park',
email: '[email protected]',
relationship: 'teammate',
});
console.log(draft.subject); // "Standup time change — moving to 3 PM"
console.log(draft.body); // "Hey John, quick heads up..."Force output JSON. Se você deixar o LLM retornar texto livre, você vai gastar metade do tempo parseando e a outra metade debugando por que quebrou.
Humano no loop
Essa é a parte que as pessoas pulam quando estão empolgadas com IA. Não pule.
Apresente o rascunho ao usuário na sua UI. Dê a ele três opções: Editar, Regenerar ou Enviar. Guarde o rascunho no servidor com um ID único pra que a ação de envio referencie um payload aprovado, não o que o cliente postar.
import { randomUUID } from 'crypto';
const drafts = new Map();
const saveDraft = (draft, recipientEmail) => {
const id = randomUUID();
drafts.set(id, {
to: recipientEmail,
subject: draft.subject,
body: draft.body,
approved: false,
createdAt: Date.now(),
});
return id;
};
const approveDraft = draftId => {
const draft = drafts.get(draftId);
if (!draft) throw new Error('Draft not found');
draft.approved = true;
return draft;
};O princípio-chave: o LLM propõe, o usuário dispõe. Nunca envie a partir de um rascunho que não foi explicitamente aprovado.
Enviando via Sendkit
Depois que o usuário aprova, envie pela API do Sendkit. Instale o SDK:
npm install @sendkitdev/sdkDepois envie o rascunho aprovado:
import { Sendkit } from '@sendkitdev/sdk';
const sendkit = new Sendkit(process.env.SENDKIT_API_KEY);
const sendApprovedDraft = async draftId => {
const draft = approveDraft(draftId);
const { data, error } = await sendkit.emails.send({
from: '[email protected]',
to: draft.to,
subject: draft.subject,
html: `<p>${draft.body.replace(/\n/g, '</p><p>')}</p>`,
text: draft.body,
});
if (error) {
console.error('Send failed:', error.message);
return { success: false, error: error.message };
}
console.log('Sent:', data.id);
return { success: true, messageId: data.id };
};O SDK retorna { data, error } em vez de dar throw. Cheque error primeiro. O valor de data.id é sua referência pra monitorar a entrega depois. Sempre inclua html e text — alguns clientes de e-mail ainda preferem texto puro. Veja nosso guia completo de e-mail transacional com Node.js pra mais padrões.

Monitorando entrega com webhooks
Enviar é metade do trabalho. Você precisa saber se o e-mail realmente chegou. Configure um endpoint de webhook no seu dashboard do Sendkit pra receber eventos de entrega.
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/sendkit', (req, res) => {
const { type, data } = req.body;
switch (type) {
case 'email.delivered':
console.log(`Delivered: ${data.messageId} to ${data.to}`);
// Update assistant context: "Your email to John was delivered"
break;
case 'email.bounced':
console.log(`Bounced: ${data.messageId} — ${data.bounceReason}`);
// Alert user: "Email to John bounced — check the address"
break;
case 'email.opened':
console.log(`Opened: ${data.messageId}`);
// Optional: "John opened your email"
break;
}
res.sendStatus(200);
});Alimente esses eventos de volta no contexto do assistente. Quando o usuário perguntar "o John recebeu meu e-mail?", o assistente pode responder com dados reais de entrega em vez de adivinhar. Para um mergulho mais profundo em tratamento de bounce, veja como lidar com bounces de e-mail.
Features inteligentes que valem a pena adicionar
Quando o loop principal funciona, sobreponha essas:
Validação de destinatário — Antes do LLM sequer rascunhar, valide o endereço do destinatário usando a API de validação de e-mail do Sendkit. Pegue erros de digitação e endereços descartáveis cedo. Sem sentido rascunhar um e-mail pra [email protected].
const { data: validation } = await sendkit.emailValidations.validate({
email: recipientEmail,
});
if (validation.result !== 'deliverable') {
// Ask user to double-check the address
}Detecção de tom — Adicione um system prompt que classifica o tom do rascunho (formal, casual, urgente) e mostra isso ao usuário. "Isso soou formal — quer que eu deixe mais casual?" Detalhe pequeno, grande ganho de usabilidade.
Sugestões de horário de envio — Se você tem dados de fuso horário do destinatário, sugira horários ideais pra envio. Terça às 10h no fuso do destinatário bate sábado à meia-noite.
Consciência de thread — Passe threads anteriores de e-mail pro contexto do LLM pra que as respostas fiquem coerentes. O assistente deve saber o que o John disse antes, não só o que o usuário quer dizer agora.
Considerações de segurança
Um assistente de e-mail com IA e acesso a uma API de envio é um tiro no pé se você for descuidado. Tranque direito.
Allowlisting de destinatário — Nunca deixe o LLM escolher o destinatário. O usuário fornece o destinatário, seu código resolve o endereço de e-mail a partir do seu banco de contatos, e o LLM só rascunha o conteúdo. Se o usuário disser "manda pra empresa inteira", isso é decisão de lógica de negócio, não do LLM.
Sanitização de conteúdo — O LLM pode gerar conteúdo tipo HTML ou tentativas de injection (prompt injection via corpo de e-mail é uma superfície de ataque real). Sanitize o corpo antes de enviar. Retire scripts, limite tags HTML e escape qualquer coisa suspeita.
Rate limiting — Limite envios por usuário por hora. Um usuário entusiasmado (ou um cliente com bug) não deveria conseguir disparar 10.000 e-mails pelo seu assistente. O pricing do Sendkit é baseado em uso, então um loop descontrolado atinge sua carteira rapidinho.
Isolamento de chave de API — Use uma chave de API dedicada do Sendkit para seu assistente com permissões de envio restritas a domínio. Não reuse a chave principal da sua aplicação. Se a chave do assistente vazar, o raio da explosão fica contido.
Logging de auditoria — Logue cada envio com ID do usuário, ID do rascunho, destinatário e timestamp. Quando algo der errado (e vai dar), você precisa de um rastro.
Juntando tudo
O padrão de assistente de e-mail com IA é quatro preocupações costuradas: interpretação de intenção, geração de conteúdo, aprovação humana e entrega confiável. O LLM cuida das duas primeiras. Seu código cuida do portão de aprovação. O Sendkit cuida da última milha.
Comece pelo loop básico de envio — rascunhar, aprovar, enviar. Adicione monitoramento de webhooks quando isso estiver funcionando. Sobreponha validação e features inteligentes depois de ter embarcado a primeira versão e ter usuários reais te dizendo o que eles realmente precisam.
A documentação completa do Sendkit cobre chaves de API, verificação de domínio, assinaturas de webhook e rate limits. Se você está construindo agentes de IA que enviam e-mail autonomamente (sem humano no loop), leia nosso guia sobre agentes de IA que enviam e-mail pra ver os guardrails adicionais que isso exige.
Construa a versão simples primeiro. Embarque. Depois deixe inteligente.
Compartilhar este artigo