Use Cases (Atualizado: 02/06/2026)

Automação de e-mail com Claude Code: de leads à monetização

Implemente automação de e-mail com Claude Code: lead magnet, consentimento, retries, analytics e receita.

Automação de e-mail com Claude Code: de leads à monetização

Automação de e-mail não é apenas disparar uma mensagem depois de um formulário. Um fluxo que sustenta receita entrega o lead magnet, inicia onboarding, acompanha pedidos de consultoria, registra consentimento, processa descadastro, remove endereços com bounce, tenta novamente falhas temporárias e mostra quais CTAs geram compra, treinamento ou conversa comercial.

Claude Code ajuda porque e-mail cruza várias partes do sistema: schemas, templates, adaptadores de provedor, fila, webhooks, eventos de analytics e documentação. Ao revisar o funil do PDF gratuito deste site, o primeiro erro foi gerar só uma função de envio com Resend. Ela funcionava, mas consentimento, URL de descadastro, tratamento de bounce e análise de CTA ficaram como remendos. O caminho melhor é pedir um plano limitado, confirmar os arquivos e só então implementar.

Este guia monta uma base Node.js/TypeScript independente de provedor, alternando APIs no estilo Resend e SendGrid. Vamos cobrir entrega de lead magnet, sequência de onboarding, follow-up de consultoria, noções de SPF/DKIM/DMARC, limites seguros de outreach, rate limits, queue/retry, templates, analytics e CTA para produtos, treinamento e consultoria. Para o funil comercial, veja também auditoria de funil de conteúdo, implementação de analytics e gestão de cookies e consentimento.

Desenhe antes de codar

Lead magnet é um recurso gratuito, como PDF, checklist ou template, entregue em troca do e-mail. Onboarding é a sequência que ajuda alguém a começar depois de se cadastrar, comprar ou entrar em um treinamento. Follow-up de consultoria é o e-mail operacional com resumo da conversa, próximos passos, proposta ou link de agenda.

Não coloque tudo em uma newsletter genérica. Consentimento, tom, métrica e risco são diferentes.

ObjetivoDestinatárioExemploCaminho de receitaRisco
Captura de leadLeitor que pediu PDFLink de download e guia relacionadoPDF grátis para produtosSalvar consentimento e URL de descadastro
OnboardingCliente ou alunoGuia inicial, checklist, bloqueios comunsTemplates, curso, suporteNão transformar recibo em venda agressiva
ConsultoriaLead qualificadoNotas, proposta, próxima agendaTreinamento e consultoriaPersonalizar com o contexto real
ReengajamentoLeitor consentido, mas inativoCaso prático ou grande atualizaçãoProduto ou consultaMonitorar frequência, bounces e opt-outs

Termos técnicos precisam ficar claros. SPF é um registro DNS que informa quais servidores podem enviar e-mail pelo seu domínio. DKIM adiciona uma assinatura para verificar autorização e integridade. DMARC define a política quando SPF ou DKIM não alinham. Bounce é falha de entrega. Rate limit é quando o provedor reduz ou rejeita requisições porque você enviou rápido demais, bateu cota ou precisa proteger reputação.

Use documentos oficiais como referência. Para Gmail, veja Google email sender guidelines. Para provedores, comece em Resend domain management ou Twilio SendGrid domain authentication. DMARC foi atualizado em 2026 pelo RFC 9989, que substitui o antigo RFC 7489. Para e-mail comercial nos EUA, confira o guia CAN-SPAM da FTC. Isto é orientação técnica, não aconselhamento jurídico.

flowchart LR
  Visitor["Leitor"]
  Form["Formulário de lead"]
  Consent["Registro de consentimento"]
  Queue["Fila de e-mail"]
  Provider["Resend / SendGrid"]
  Inbox["Caixa de entrada"]
  Webhook["Eventos"]
  Analytics["Analytics"]
  Offer["Produto / treinamento / consultoria"]

  Visitor --> Form --> Consent --> Queue --> Provider --> Inbox
  Provider --> Webhook --> Analytics --> Offer
  Inbox --> Offer

Prompt para Claude Code

Um pedido vago gera só uma função de envio. Um pedido bom define objetivo, fronteiras e verificação.

Implemente automação de e-mail neste repositório.
Objetivos: entrega de lead magnet, sequência de onboarding com 3 e-mails e follow-up de consultoria.

Restrições:
- Usar Node.js 20+ e TypeScript.
- Criar adapter para alternar entre API estilo Resend e estilo SendGrid.
- API keys só no servidor, via variáveis de ambiente.
- Criar schemas para lead, email job, unsubscribe e provider event.
- Repetir 429 e 5xx com exponential backoff.
- Não enviar para endereços com unsubscribe, complaint ou suppression.
- Colocar hard bounces repetidos em suppression list.
- Incluir texto, HTML, URL de descadastro e remetente claro.
- Documentar links oficiais de provedor e autenticação.
- Adicionar scripts executáveis e testes focados.

Primeiro mostre tabela de design e lista de arquivos. Aguarde aprovação antes de editar.

Starter copiável

O exemplo usa um JSON local como fila para rodar em demo. Em produção, troque por Postgres, Redis, SQS, Cloud Tasks ou outra fila durável com lock e auditoria.

{
  "type": "module",
  "scripts": {
    "lead:send": "tsx scripts/send-lead-magnet.ts",
    "email:worker": "tsx scripts/email-worker.ts"
  },
  "dependencies": {
    "zod": "latest"
  },
  "devDependencies": {
    "@types/node": "latest",
    "tsx": "latest",
    "typescript": "latest"
  }
}
// src/email/schema.ts
import { z } from "zod";

export const leadSchema = z.object({
  email: z.string().email(),
  name: z.string().trim().min(1).max(80),
  locale: z.enum(["ja", "en", "zh", "ko", "es", "fr", "de", "pt", "hi", "id"]).default("pt"),
  source: z.enum(["article", "product", "workshop", "consultation"]),
  consentAt: z.string().datetime(),
  tags: z.array(z.string()).default([]),
});

export const sendMessageSchema = z.object({
  to: z.string().email(),
  from: z.string().email(),
  fromName: z.string().min(1),
  replyTo: z.string().email().optional(),
  subject: z.string().min(1).max(120),
  text: z.string().min(1),
  html: z.string().min(1),
  unsubscribeUrl: z.string().url(),
  category: z.enum(["lead_magnet", "onboarding", "consultation_followup"]),
  metadata: z.record(z.string()).default({}),
});

export const emailJobSchema = z.object({
  message: sendMessageSchema,
  maxAttempts: z.number().int().min(1).max(8).default(4),
});

export type SendMessage = z.infer<typeof sendMessageSchema>;
export type EmailJobInput = z.infer<typeof emailJobSchema>;
// src/email/provider.ts
import { randomUUID } from "node:crypto";
import type { SendMessage } from "./schema";

type SendResult = { providerMessageId: string; acceptedAt: string };
export interface EmailProvider { send(message: SendMessage): Promise<SendResult>; }

function requiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env: ${name}`);
  return value;
}

async function parseProviderError(response: Response): Promise<Error> {
  const body = await response.text().catch(() => "");
  const retryable = response.status === 429 || response.status >= 500;
  const error = new Error(`Email provider error ${response.status}: ${body || response.statusText}`);
  (error as Error & { retryable?: boolean }).retryable = retryable;
  return error;
}

export class ResendProvider implements EmailProvider {
  async send(message: SendMessage): Promise<SendResult> {
    const response = await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${requiredEnv("RESEND_API_KEY")}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: `${message.fromName} <${message.from}>`,
        to: [message.to],
        reply_to: message.replyTo,
        subject: message.subject,
        text: message.text,
        html: message.html,
        headers: {
          "List-Unsubscribe": `<${message.unsubscribeUrl}>`,
          "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
        },
      }),
    });
    if (!response.ok) throw await parseProviderError(response);
    const data = (await response.json().catch(() => ({}))) as { id?: string };
    return { providerMessageId: data.id ?? randomUUID(), acceptedAt: new Date().toISOString() };
  }
}

export class SendGridProvider implements EmailProvider {
  async send(message: SendMessage): Promise<SendResult> {
    const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${requiredEnv("SENDGRID_API_KEY")}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        personalizations: [{ to: [{ email: message.to }], custom_args: message.metadata }],
        from: { email: message.from, name: message.fromName },
        reply_to: message.replyTo ? { email: message.replyTo } : undefined,
        subject: message.subject,
        content: [
          { type: "text/plain", value: message.text },
          { type: "text/html", value: message.html },
        ],
        headers: {
          "List-Unsubscribe": `<${message.unsubscribeUrl}>`,
          "List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
        },
      }),
    });
    if (!response.ok) throw await parseProviderError(response);
    return { providerMessageId: response.headers.get("x-message-id") ?? randomUUID(), acceptedAt: new Date().toISOString() };
  }
}

export function createEmailProvider(): EmailProvider {
  return process.env.EMAIL_PROVIDER === "sendgrid" ? new SendGridProvider() : new ResendProvider();
}
// src/email/queue.ts
import { readFile, writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { emailJobSchema, type EmailJobInput } from "./schema";

type StoredJob = EmailJobInput & {
  id: string;
  status: "scheduled" | "processing" | "sent" | "failed";
  attempts: number;
  nextAttemptAt: string;
  lastError?: string;
};

const queueFile = process.env.EMAIL_QUEUE_FILE ?? ".email-queue.json";

async function loadQueue(): Promise<StoredJob[]> {
  if (!existsSync(queueFile)) return [];
  return JSON.parse(await readFile(queueFile, "utf8")) as StoredJob[];
}

async function saveQueue(jobs: StoredJob[]) {
  await writeFile(queueFile, JSON.stringify(jobs, null, 2) + "\n");
}

export async function enqueueEmail(input: EmailJobInput) {
  const parsed = emailJobSchema.parse(input);
  const jobs = await loadQueue();
  const job: StoredJob = { ...parsed, id: randomUUID(), status: "scheduled", attempts: 0, nextAttemptAt: new Date().toISOString() };
  jobs.push(job);
  await saveQueue(jobs);
  return job.id;
}

export async function claimDueJobs(limit = 5): Promise<StoredJob[]> {
  const now = Date.now();
  const jobs = await loadQueue();
  const due = jobs.filter((job) => job.status === "scheduled" && Date.parse(job.nextAttemptAt) <= now).slice(0, limit);
  for (const job of due) job.status = "processing";
  await saveQueue(jobs);
  return due;
}

export async function completeJob(id: string) {
  const jobs = await loadQueue();
  const job = jobs.find((item) => item.id === id);
  if (job) job.status = "sent";
  await saveQueue(jobs);
}

export async function failJob(id: string, error: unknown) {
  const jobs = await loadQueue();
  const job = jobs.find((item) => item.id === id);
  if (!job) return;
  job.attempts += 1;
  job.lastError = error instanceof Error ? error.message : String(error);
  if (job.attempts >= job.maxAttempts) {
    job.status = "failed";
  } else {
    const delayMs = Math.min(15 * 60_000, 2 ** job.attempts * 1000);
    job.status = "scheduled";
    job.nextAttemptAt = new Date(Date.now() + delayMs).toISOString();
  }
  await saveQueue(jobs);
}
// scripts/email-worker.ts
import { claimDueJobs, completeJob, failJob } from "../src/email/queue";
import { createEmailProvider } from "../src/email/provider";

const provider = createEmailProvider();
const jobs = await claimDueJobs(Number(process.env.EMAIL_WORKER_BATCH ?? 3));

for (const job of jobs) {
  try {
    const result = await provider.send(job.message);
    await completeJob(job.id);
    console.log(`sent ${job.id} as ${result.providerMessageId}`);
  } catch (error) {
    await failJob(job.id, error);
    console.error(`failed ${job.id}`, error);
  }
}

Teste primeiro com um endereço seu. Não envie a leitores reais antes de autenticar o domínio e validar o descadastro.

npm install
EMAIL_TO=you@example.com APP_URL=https://example.com npm run lead:send
EMAIL_PROVIDER=resend RESEND_API_KEY=re_xxx npm run email:worker

Bounces, descadastros e analytics

A resposta positiva do provedor só diz que a requisição foi aceita. Não prova leitura, clique ou interesse. Normalize webhooks para um modelo interno e use suppression list para bounce duro, complaint e unsubscribe.

// src/email/events.ts
import { z } from "zod";

const providerEventSchema = z.object({
  provider: z.enum(["resend", "sendgrid", "unknown"]),
  type: z.enum(["delivered", "bounce", "complaint", "unsubscribe", "open", "click", "deferred"]),
  email: z.string().email().optional(),
  providerMessageId: z.string().optional(),
  reason: z.string().optional(),
  occurredAt: z.string().datetime(),
});

export function normalizeProviderEvent(payload: unknown) {
  const raw = payload as Record<string, unknown>;
  const type = String(raw.type ?? raw.event ?? "delivered");
  const mappedType =
    type.includes("bounce") ? "bounce" :
    type.includes("complaint") || type.includes("spam") ? "complaint" :
    type.includes("unsubscribe") ? "unsubscribe" :
    type.includes("click") ? "click" :
    type.includes("open") ? "open" :
    type.includes("defer") ? "deferred" :
    "delivered";

  return providerEventSchema.parse({
    provider: raw.sg_event_id ? "sendgrid" : raw.created_at ? "resend" : "unknown",
    type: mappedType,
    email: String(raw.email ?? raw.recipient ?? "") || undefined,
    providerMessageId: String(raw.email_id ?? raw.sg_message_id ?? ""),
    reason: typeof raw.reason === "string" ? raw.reason : undefined,
    occurredAt: new Date(String(raw.created_at ?? Date.now())).toISOString(),
  });
}

Não dependa só de abertura. Bloqueio de imagem e privacidade distorcem esse número. Meça download, clique em CTA, início de formulário de consultoria, resposta, descadastro, bounce e compra. Use eventos como lead_magnet_requested, email_cta_click e consultation_request_started.

Casos de uso

Primeiro: PDF gratuito no fim de artigos técnicos. Envie o download imediatamente, depois um erro comum de configuração, depois um template de produto e por fim convite para treinamento ou consultoria. Cada e-mail deve ter uma ação principal e link de descadastro.

Segundo: onboarding após compra. Quem comprou um guia ou entrou em workshop precisa começar, resolver bloqueios e ver uso avançado. Ajudar o comprador a ter sucesso é melhor que transformar recibo em propaganda.

Terceiro: follow-up de consultoria. Inclua notas da reunião, decisões, próximos passos, links, prazo e CTA de agenda ou proposta. Se não refletir a conversa real, parecerá spam.

Quarto: reengajamento de baixa frequência. Para leitores consentidos, mas inativos, envie só grandes atualizações, histórias de falha úteis ou novos recursos. Se cliques e respostas não voltarem, reduza ou pare.

Falhas comuns

A primeira falha é expor a API key no navegador. Envio de e-mail deve ficar no servidor.

A segunda é enviar sem autenticar domínio. Configure SPF, DKIM e DMARC.

A terceira é ignorar descadastro e bounce. Unsubscribe, complaint e hard bounce devem sair das campanhas comuns.

A quarta é repetir imediatamente após rate limit. Trate 429 e 5xx temporários com backoff. Limites exatos variam por conta, plano, reputação e destinatário.

A quinta é misturar transacional e promocional. Reset de senha, recibo e alerta de conta devem ser claros. CTAs comerciais pertencem a mensagens com consentimento e contexto.

CTA de monetização

O sistema está pronto quando o leitor consegue escolher o próximo passo naturalmente. No ClaudeCodeLab, iniciantes começam pelo PDF grátis, builders veem produtos e templates, e equipes usam treinamento e consultoria para aplicar isso a um repositório real.

Na prática, o maior ganho veio de desenhar consentimento, descadastro, bounce e analytics de CTA antes do código do provedor. Comece com um único e-mail de lead magnet, valide envio, descadastro, bounce e clique, e depois expanda para onboarding e consultoria.

#Claude Code #automação de e-mail #lead capture #Resend #SendGrid #Node.js
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.