Build This Now
Build This Now
O que é o Código Claude?Instalar o Claude CodeInstalador Nativo do Claude CodeO Teu Primeiro Projeto com Claude Code
Failed Payment RecoveryLocalization and i18nPDF InvoicesReferral ProgramTwo-Factor AuthNext.js DevTools MCPNext.js Agent SetupSubscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows: Como Orquestrar 1.000 Subagentes Num Codebase RealMelhores Práticas do Claude CodeBoas Práticas para o Claude Opus 4.7Claude Code num VPSIntegração GitRevisão de Código com ClaudeWorktrees no Claude CodeControle Remoto do Claude CodeChannels do Claude CodeChannels, Routines, Teleport, DispatchTarefas Agendadas no Claude CodePermissões do Claude CodeModo Auto do Claude CodeAdicionar Pagamentos Stripe Com o Claude CodeFeedback LoopsFluxos de Trabalho com TodosTarefas no Claude CodeTemplates de ProjetoPreços e Consumo de Tokens no Claude CodePreços do Claude Code: O Que Vais Mesmo PagarClaude Code Ultra ReviewConstruir Uma App Next.js Com o Claude CodeSupabase DatabaseVercel DeepsecTest-Driven DevelopmentComo Construir um MVP de SaaS Com o Claude CodeAdicionar Autenticação Com o Claude Code (Supabase Auth)Adicionar Email Transacional Com o Claude Code (Resend + React Email)Construir Uma API Type-Safe Com o Claude Code (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideComércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingQuanto Custa Construir um SaaS com Claude Code em 2026Parallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Transactional Email

Adicionar Email Transacional Com o Claude Code (Resend + React Email)

Como construir emails de boas-vindas, recibos e reposição de password numa app Next.js 16 usando o Resend e o React Email, com o Claude Code a escrever os templates e a lógica de envio.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 20269 min readHandbook hubWorkflow index

Todo o SaaS precisa de três emails no primeiro dia: boas-vindas, recibo, reposição de password. Errá-los (aterrar no spam, faltar um registo DKIM, falhar em silêncio por causa de um erro de escrita) custa-te utilizadores antes mesmo de acabarem o onboarding. Este é um passo a passo completo para ligar o Resend e o React Email numa app Next.js 16 com o Claude Code, desde a verificação do domínio até a um envio a funcionar dentro de uma Server Action.

Porque É Que o Email Transacional É Chato

O email transacional parece simples até o pores mesmo em produção. Precisas de um domínio de envio verificado ou os teus emails vão para o spam, ou nem sequer são enviados. Precisas de HTML que renderize de forma consistente no Gmail, no Outlook e no Apple Mail, que têm suporte de CSS radicalmente diferente. Precisas de disparar o envio no momento certo (depois de o registo terminar, depois de um pagamento ter sucesso) sem bloquear a resposta que o utilizador está à espera. E precisas de saber quando um envio falhou de verdade em vez de assumir que correu bem.

O Resend trata da entrega e da reputação do domínio. O React Email trata dos templates, para que escrevas JSX em vez de layouts de tabela feitos à mão. O Claude Code é útil aqui porque já conhece bem ambas as APIs e consegue ligar a canalização (Server Actions, tratamento de erros, variáveis de ambiente) corretamente à primeira, desde que lhe digas exatamente o que construir.

Instalar o Resend e o React Email

Precisas de três pacotes: o SDK do Resend para enviar email, a biblioteca de componentes do React Email para construir templates, e a CLI do React Email para os pré-visualizar localmente. Pede ao Claude Code para instalar os três e ele usa o gestor de pacotes do teu projeto automaticamente.

npm install resend @react-email/components
npm install -D react-email

O resend é o cliente que fala com a API do Resend. O @react-email/components dá-te primitivas pré-construídas e seguras para email (Html, Body, Container, Button, Text) que renderizam corretamente nos vários clientes de email sem teres de lutar com CSS inline. O react-email é uma dependência só de desenvolvimento, é ela que alimenta o servidor de pré-visualização local que vais usar mais à frente.

Obter uma Chave de API e Verificar o Teu Domínio de Envio

Regista-te em resend.com e cria uma chave de API a partir do dashboard. Guarda-a como uma variável de ambiente, nunca a metas hardcoded num template nem faças commit dela.

# .env.local
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
EMAIL_FROM="Your App <hello@yourdomain.com>"

Sem um domínio verificado, o Resend só te deixa enviar para o email da tua própria conta usando o endereço partilhado onboarding@resend.dev. Isso serve para um primeiro teste, mas os utilizadores reais não vão receber nada a partir daí. Para enviar para caixas de entrada reais, vai a Domains no dashboard do Resend, adiciona o teu domínio, e adiciona os registos de SPF e DKIM que ele te dá ao teu fornecedor de DNS. A verificação costuma levar uns minutos assim que os registos se propagam, ocasionalmente mais dependendo do teu registrar.

Diz ao Claude Code que domínio estás a usar e ele vai lembrar-te de verificar o estado de verificação antes de testar um envio real:

claude "add a startup check that warns in the console if EMAIL_FROM's domain isn't verified in Resend, using the domains.list API"

Construir um Template de React Email

Os templates vivem na sua própria pasta para que o servidor de pré-visualização os encontre e para que fiquem separados dos componentes de UI da tua app. Cada template é um componente React simples construído a partir das primitivas do @react-email/components, que mapeiam para HTML seguro para email nos bastidores.

// emails/welcome-email.tsx
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Preview,
  Section,
  Text,
} from "@react-email/components";

interface WelcomeEmailProps {
  name: string;
  dashboardUrl: string;
}

export default function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Your account is ready</Preview>
      <Body style={{ backgroundColor: "#f6f6f6", fontFamily: "sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "32px", borderRadius: "8px" }}>
          <Heading style={{ fontSize: "20px" }}>Welcome, {name}</Heading>
          <Text style={{ color: "#444", fontSize: "15px", lineHeight: "22px" }}>
            Your account is set up and ready to go. Click below to get started.
          </Text>
          <Section style={{ marginTop: "24px" }}>
            <Button
              href={dashboardUrl}
              style={{
                backgroundColor: "#111",
                color: "#fff",
                padding: "12px 20px",
                borderRadius: "6px",
                fontSize: "14px",
              }}
            >
              Go to dashboard
            </Button>
          </Section>
        </Container>
      </Body>
    </Html>
  );
}

Os estilos inline aqui são intencionais, não um atalho. A maioria dos clientes de email retira os blocos <style> ou o CSS baseado em classes, por isso o @react-email/components renderiza os teus estilos inline no HTML final automaticamente. Escreves JSX de aspeto normal e sai seguro para email.

Um Auxiliar lib/email

Todas as rotas que enviam email precisam do mesmo cliente Resend, por isso ele pertence a um único auxiliar partilhado em vez de ser reinstanciado por todo o lado. Isto também te dá um único sítio para adicionar logging ou um fallback mais tarde.

// lib/email.ts
import { Resend } from "resend";
import type { ReactElement } from "react";

const resend = new Resend(process.env.RESEND_API_KEY);

interface SendEmailParams {
  to: string;
  subject: string;
  react: ReactElement;
}

export async function sendEmail({ to, subject, react }: SendEmailParams) {
  const { data, error } = await resend.emails.send({
    from: process.env.EMAIL_FROM!,
    to,
    subject,
    react,
  });

  if (error) {
    console.error("Email send failed:", error);
    return { success: false as const, error };
  }

  return { success: true as const, id: data?.id };
}

O SDK do Resend não lança exceção num envio falhado, devolve um campo error no objeto de resposta. Se saltares essa verificação, um email devolvido ou rejeitado parece exatamente igual a um bem-sucedido nos teus logs. O auxiliar acima obriga cada quem chama a tratar ambos os casos de forma explícita.

Enviar a Partir de uma Server Action (Email de Boas-Vindas Depois do Registo)

O email de boas-vindas deve disparar logo a seguir à criação da conta, sem fazer o utilizador esperar pela resposta do fornecedor de email antes de aterrar na app. Envolve o envio na Server Action que corre depois de o registo terminar.

// app/(auth)/signup/actions.ts
"use server";

import { createUser } from "@/lib/db";
import { sendEmail } from "@/lib/email";
import WelcomeEmail from "@/emails/welcome-email";
import { redirect } from "next/navigation";

export async function signUp(formData: FormData) {
  const email = formData.get("email") as string;
  const name = formData.get("name") as string;
  const password = formData.get("password") as string;

  const user = await createUser({ email, name, password });

  const result = await sendEmail({
    to: user.email,
    subject: "Welcome aboard",
    react: WelcomeEmail({
      name: user.name,
      dashboardUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
    }),
  });

  if (!result.success) {
    console.error(`Welcome email failed for user ${user.id}`, result.error);
  }

  redirect("/dashboard");
}

Um email de boas-vindas falhado nunca deve bloquear a criação da conta. O utilizador já tem uma conta nesse ponto, por isso o código regista a falha e continua para o dashboard em vez de lançar uma exceção. Aquela linha de log importa, pede ao Claude Code para a ligar ao teu rastreador de erros existente, se tiveres um, para que um pico de falhas de email seja mesmo notado em vez de ficar quietinho nos logs do servidor.

Enviar a Partir de um Route Handler (Reposição de Password)

A reposição de password precisa de um token gerado antes do envio, e é muitas vezes disparada a partir de um formulário que espera uma resposta JSON em vez de um redirect, por isso um Route Handler encaixa melhor aqui do que uma Server Action.

// app/api/auth/reset-password/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createResetToken, getUserByEmail } from "@/lib/db";
import { sendEmail } from "@/lib/email";
import ResetPasswordEmail from "@/emails/reset-password-email";

export async function POST(request: NextRequest) {
  const { email } = await request.json();
  const user = await getUserByEmail(email);

  // Always return success, even if the user doesn't exist.
  // This avoids leaking which emails have accounts.
  if (!user) {
    return NextResponse.json({ success: true });
  }

  const token = await createResetToken(user.id);
  const resetUrl = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password/${token}`;

  const result = await sendEmail({
    to: user.email,
    subject: "Reset your password",
    react: ResetPasswordEmail({ resetUrl }),
  });

  if (!result.success) {
    return NextResponse.json({ success: false, error: "Failed to send email" }, { status: 500 });
  }

  return NextResponse.json({ success: true });
}

Devolver a mesma resposta de sucesso quer a conta exista ou não é um detalhe de segurança pequeno mas real. Pede ao Claude Code este padrão de forma explícita ("don't leak account existence through the response") porque uma implementação ingénua devolve um 404 para emails desconhecidos, que é exatamente a informação que não queres entregar.

Construir o Email de Recibo

Os recibos seguem o mesmo padrão do email de boas-vindas, apenas com conteúdo diferente e, normalmente, um webhook do Stripe como disparo em vez de uma submissão de formulário.

// emails/receipt-email.tsx
import { Body, Container, Head, Heading, Hr, Html, Row, Column, Section, Text } from "@react-email/components";

interface ReceiptEmailProps {
  amount: string;
  planName: string;
  invoiceDate: string;
}

export default function ReceiptEmail({ amount, planName, invoiceDate }: ReceiptEmailProps) {
  return (
    <Html>
      <Head />
      <Body style={{ backgroundColor: "#f6f6f6", fontFamily: "sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "32px", borderRadius: "8px" }}>
          <Heading style={{ fontSize: "20px" }}>Payment received</Heading>
          <Section style={{ marginTop: "16px" }}>
            <Row>
              <Column><Text>Plan</Text></Column>
              <Column><Text>{planName}</Text></Column>
            </Row>
            <Row>
              <Column><Text>Amount</Text></Column>
              <Column><Text>{amount}</Text></Column>
            </Row>
            <Row>
              <Column><Text>Date</Text></Column>
              <Column><Text>{invoiceDate}</Text></Column>
            </Row>
          </Section>
          <Hr />
          <Text style={{ color: "#888", fontSize: "12px" }}>
            Questions about this charge? Reply to this email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Chama isto a partir do teu handler de webhook do Stripe depois de um evento checkout.session.completed ou invoice.paid, usando o mesmo auxiliar sendEmail de antes. O Stripe repete os webhooks falhados, por isso se o teu envio de email lançar uma exceção dentro desse handler, envolve-o para que um envio mau não faça o Stripe pensar que o webhook inteiro falhou e repita o evento todo.

Pré-visualizar os Templates Localmente

Não queres enviar um email real cada vez que ajustas o tamanho de uma fonte. A CLI do React Email arranca um servidor local que renderiza os teus templates ao vivo e recarrega ao guardar, sem chave de API nem chamada de rede necessárias.

npx react-email dev

Isto abre uma pré-visualização em localhost:3000 (ou a próxima porta livre) a mostrar todos os componentes na tua pasta emails/, renderizados como apareceriam numa caixa de entrada, com um toggle para inspecionar o output HTML puro. Aponta-o para uma pasta personalizada com --dir se os teus templates não viverem na localização predefinida. Pede ao Claude Code para passar props de teste para cada template para que a pré-visualização mostre conteúdo de aspeto real em vez de campos vazios.

Tratar Erros e Idempotência

Dois modos de falha importam em produção. Primeiro, uma chamada lenta ou falhada ao Resend dentro de uma Server Action pode deixar um pedido pendurado se não definires uma expectativa razoável de quanto tempo vais esperar. Segundo, as repetições (um utilizador a fazer duplo clique no submit, um webhook a disparar duas vezes) podem provocar envios duplicados se nada as parar.

Para proteção contra duplicados, passa uma chave de idempotência nos envios disparados por eventos externos como webhooks, para que um webhook repetido não envie o mesmo recibo duas vezes.

await resend.emails.send(
  {
    from: process.env.EMAIL_FROM!,
    to: user.email,
    subject: "Payment received",
    react: ReceiptEmail({ amount, planName, invoiceDate }),
  },
  { idempotencyKey: `receipt-${invoiceId}` }
);

Usar o ID da fatura do Stripe como chave de idempotência significa que o Resend reconhece um pedido repetido com a mesma chave e salta o envio, em vez de disparar um segundo recibo. Isto importa mais nos envios disparados por webhook, onde as repetições são comportamento esperado, não um caso raro.

Testar Antes de Pores em Produção

Antes de ligares isto ao registo ou ao checkout, envia um email de teste real para a tua própria caixa de entrada e verifica três coisas à mão: a linha de assunto não está truncada, o botão liga mesmo ao URL certo, e o email não aterra no spam. O Gmail e o Outlook tratam os estilos inline de forma diferente, por isso verifica ambos se conseguires.

claude "send a test welcome email to my inbox using the sendEmail helper and confirm the response includes a data.id"

Assim que isso funcionar, verifica o separador Logs do dashboard do Resend. Cada envio mostra o seu estado de entrega (delivered, bounced, complained), que é a forma mais rápida de confirmar que a verificação do teu domínio está mesmo a funcionar de ponta a ponta, não apenas aceite pela API.

Ligar o email à mão, um endpoint de cada vez, é o tipo de tarefa que o Claude Code trata bem numa única sessão. A parte mais difícil é tudo à volta: verificar o domínio corretamente, garantir que um envio falhado não parte o registo em silêncio, e manter os templates consistentes à medida que adicionas mais. O Code Kit de $29 entrega isto já ligado à framework que ele configura, templates de boas-vindas, recibo e reposição incluídos, para que comeces a partir de um sistema de email a funcionar em vez de construíres um do zero.

Posted by @speedy_devv

Continue in Workflow

  • Comércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar
    Um guia em português simples sobre comércio agêntico em 2026: o que fazem o x402, o ACP e o Machine Payments Protocol, mais um passo a passo de fim de semana para lançar uma API paga que agentes de IA podem comprar.
  • Melhores Práticas do Claude Code
    Cinco hábitos separam os engenheiros que entregam com Claude Code: PRDs, regras modulares em CLAUDE.md, slash commands personalizados, resets com /clear e uma mentalidade de evolução do sistema.
  • Modo Auto do Claude Code
    Um segundo modelo Sonnet revê cada chamada de ferramenta do Claude Code antes de ser executada. O que o modo auto bloqueia, o que permite e as regras de permissão que cria nas tuas definições.
  • Channels, Routines, Teleport, Dispatch
    As quatro funcionalidades de Claude Code que a Anthropic lançou em março e abril de 2026 e que transformam a CLI numa camada de coordenação orientada a eventos entre telemóvel, web e desktop.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Best SaaS Boilerplate 2026: The Honest Comparison
    An honest 2026 roundup of the best SaaS boilerplates and starter kits (ShipFast, Makerkit, Supastarter, SaaS Pegasus, Divjoy, open source), with real pricing, stacks, and the trade-off nobody mentions: a boilerplate still leaves you coding.
  • Changelog do Claude Code
    Notas de cada versão do Claude Code desde a beta v0.2 até março de 2026. Bare mode, relay de permissões com Channels, correções de OAuth e todas as breaking changes.
  • What It Really Costs to Build a SaaS MVP in 2026
    A buyer's cost breakdown for building a SaaS MVP in 2026. Real freelancer, agency, no-code, AI-tool, and DIY numbers with citations, plus where the money actually goes.
  • Templates de Prompts que Entregam Código
    Dez receitas de prompts que entregam código: scaffolding full-stack, APIs, schemas, testes, refatorações, debugging, reviews e CI. Cada uma com os erros a evitar.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo

Adicionar Autenticação Com o Claude Code (Supabase Auth)

Adiciona registo por email/password, Google OAuth, magic links, rotas protegidas e gestão de sessões a uma app Next.js 16 usando o Claude Code e o Supabase Auth.

Construir Uma API Type-Safe Com o Claude Code (oRPC + Zod)

Como construir uma camada de API totalmente type-safe em Next.js 16 com oRPC e Zod, usando o Claude Code, para que as mudanças de esquema partam a build em vez da produção.

On this page

Porque É Que o Email Transacional É Chato
Instalar o Resend e o React Email
Obter uma Chave de API e Verificar o Teu Domínio de Envio
Construir um Template de React Email
Um Auxiliar lib/email
Enviar a Partir de uma Server Action (Email de Boas-Vindas Depois do Registo)
Enviar a Partir de um Route Handler (Reposição de Password)
Construir o Email de Recibo
Pré-visualizar os Templates Localmente
Tratar Erros e Idempotência
Testar Antes de Pores em Produção

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →