# E-mails no código: veja, migre e melhore

URL canônica: https://brew.new/pt-br/blog/emails-in-code-visibility-migration-mcp
Por Thomas Park
Publicado em 2026-10-12
Atualizado em 2026-10-15

E-mails no código são e-mails do produto definidos na sua base de código e lançados como qualquer outra funcionalidade. Numa equipe que usa Resend e React Email, pergunte quais e-mails o produto envia e a resposta demora a vir. O e-mail de boas-vindas é um componente em `emails/`. A redefinição de senha fica no painel do provedor de autenticação. O e-mail de fim do período de teste é uma string em um cron job. O Stripe envia o recibo. Ninguém lembra o que envia o e-mail “sentimos a sua falta”.

Cada e-mail foi fácil de lançar, então ninguém tem a visão completa. Nós resolveríamos isso nesta ordem: visibilidade, depois migração, depois melhoria. O agente de programação que você já usa, conectado ao Brew via MCP, consegue fazer a maior parte do trabalho.

## Pontos-chave

- Comece por um inventário, não por uma ferramenta nova: cada e-mail, o gatilho, o arquivo e as variáveis dele.
- Um agente de programação consegue encontrar as chamadas de envio, renderizar cada modelo e importá-lo com a ferramenta MCP `import_email`. A importação é determinística, não usa IA e é gratuita.
- Os e-mails importados ficam em um canvas por grupo, então a equipe os revisa lado a lado, no desktop e no celular.
- Para migrar, um envio no estilo do Resend vira um gatilho mais uma automação publicada, disparada com uma única chamada REST. O código de produção usa REST, não MCP.
- Migre um tipo de e-mail por vez, atrás de uma feature flag, e deixe o e-mail transacional por último.

## Por que o e-mail definido no código fica invisível

O React Email e o Resend tornaram o e-mail muito melhor para os desenvolvedores: componentes tipados, revisados em pull requests. O custo aparece depois.

**Os e-mails ficam espalhados.** Componentes, HTML inline em uma tarefa em segundo plano, modelos no painel de um terceiro, e-mails que outro serviço envia por você. Nenhum arquivo lista todos eles.

**A prévia não é a produção.** O servidor de prévia do React Email mostra um modelo com as props de prévia dele, não o que o cliente recebeu, e nada dos outros modelos.

**Só os engenheiros conseguem ver.** Produto, design e suporte teriam que pedir a um engenheiro para rodar o projeto, então quase nunca pedem.

**Nada é revisado depois do lançamento.** O e-mail de dois anos atrás ainda tem o logo antigo e um plano que você não vende mais. Ninguém percebe, porque ninguém vê os e-mails juntos.

## Passo 1: visibilidade

O objetivo é ter uma visão compartilhada de todos os e-mails. Nada aqui mexe na produção.

![As três fases para tirar os e-mails do código: visibilidade, com o import_email e um canvas por grupo; migração, com domínios, contatos, gatilhos e automações; e otimização, com auditorias, testes de renderização e análises.](/images/blog/emails-in-code-three-phases.png)

*Cada fase é útil por si só, e você pode parar depois de qualquer uma delas.*

### Conecte o seu agente de programação ao Brew

O servidor MCP do Brew fica em `https://brew.new/api/mcp` e usa OAuth: você faz login no Brew e escolhe a marca em que o agente pode trabalhar. No Claude Code:

```bash
claude mcp add --transport http brew "https://brew.new/api/mcp"
```

O comando só salva o servidor na configuração local deste projeto. Para usá-lo em todos os projetos, adicione `--scope user`. Antes de usar as ferramentas, execute `claude mcp login brew` ou abra `/mcp` e autentique a conexão do Brew.

A [documentação de configuração dos clientes](https://docs.brew.new/api-reference/mcp/connect-your-client) tem instruções para o Cursor e o Codex. Clientes sem OAuth podem usar uma chave de API restrita à marca. Depois, peça ao agente que chame `get_brew_capabilities`, para que ele leia a lista de ferramentas atual em vez de adivinhar.

### Monte o inventário

Peça uma lista ao agente:

```text
Find every email this codebase sends. Search for provider calls
(resend.emails.send, postmark, sendgrid, nodemailer, SES), React Email
components and render() calls, and any email templates referenced from
config or a third-party dashboard.

For each email, record: a name, what triggers it, the file and line that
sends it, the template file, the provider, and the variables it uses.
Write the result as a table in EMAILS.md. List anything you could not
resolve under "Unknown" instead of guessing.
```

Mantenha a seção “Unknown” (o que o agente não conseguiu identificar). As confirmações de autenticação e os recibos do Stripe não são chamadas de envio no seu código, mas fazem parte da lista. Faça o commit do `EMAILS.md`. Se você parar aqui, ainda terá uma lista que não tinha ontem.

### Renderize cada modelo com dados realistas

A melhor entrada para a importação é o HTML que o seu código já produz. O [utilitário render](https://react.email/docs/utilities/render) do React Email devolve essa string:

```tsx
import { render } from 'react-email'
import WelcomeEmail from './emails/welcome'

const html = await render(
  <WelcomeEmail firstName="Ana" planName="Pro" trialDays={14} />
)
```

Projetos mais antigos importam `render` de `@react-email/render`. Use dados de exemplo realistas, e não `"test"`, já que a sua equipe vai revisar esses e-mails. Peça ao agente um script que renderize cada modelo em um arquivo.

O Brew também importa diretamente um módulo do React Email com `format: "jsx"`, lendo-o de forma estática, sem executá-lo. As props assumem os valores padrão ou os `PreviewProps`, componentes importados de outros arquivos locais são descartados ou rejeitados, e `await` e recursos parecidos são recusados. Para qualquer coisa além de um único arquivo autossuficiente, use o HTML renderizado.

### Importe tudo para um só lugar

Depois, importe cada renderização com `import_email`, com um `groupName` por área do produto:

```text
For each rendered email in ./email-renders, call import_email with
format "html", the title and subject line from EMAILS.md, and a
groupName by area: "Auth", "Onboarding", "Billing" or "Lifecycle".
Report the emailId for each one and any warnings in its assetReport.
```

Os desenvolvedores perguntam, com razão, se a importação reescreve os e-mails deles. Não reescreve:

- É um compilador determinístico, não um modelo de IA, e não consome créditos.
- Ela produz um design editável no Brew, enquanto o envio mantém o HTML original nas linhas importadas.
- Ela hospeda de novo as imagens públicas, e o `assetReport` lista as que não conseguiu buscar.
- Ela remove scripts, iframes, formulários e manipuladores de eventos, e neutraliza os pixels de rastreamento de abertura que já existiam.
- Cada código-fonte pode ter até 5 MB. Não há entrada por URL: a ferramenta recebe o próprio markup, e o `baseUrl` só resolve caminhos relativos de imagens.

### Revise os e-mails em equipe

Cada grupo ganha um canvas. Abra `brew.new/emails/onboarding` e a sequência inteira aparece lado a lado, com uma alternância entre desktop e celular e comentários em cada design. Os links do canvas funcionam para quem está conectado ao seu espaço de trabalho do Brew. Não há link de compartilhamento público, então convide quem vai revisar.

É aqui que os problemas aparecem: dois logos, um botão com link quebrado, três rodapés. Rode o `audit_email` antes de corrigir qualquer coisa. Ele verifica links, imagens, conformidade, tamanho carregado, compatibilidade com os clientes de e-mail, acessibilidade e redação, custa 5 créditos por auditoria completa e aceita um `emailId` salvo ou HTML puro, então você pode auditar e-mails que ainda não importou. O nosso [checklist de QA antes do envio](/pt-br/blog/pre-send-email-qa-checklist) explica cada verificação e cada limite.

Agora você tem um inventário, todos os e-mails em um canvas e uma lista de problemas, e o seu código continua enviando tudo como antes.

## Passo 2: migração

Agora o Brew passa a enviar alguns e-mails no lugar do seu código. Nem todo e-mail precisa migrar, e só um sistema envia cada tipo de e-mail em um dado momento.

### Mapeie os conceitos

| No seu código hoje | No Brew |
| --- | --- |
| `resend.emails.send({ react: <WelcomeEmail /> })` | Um evento de gatilho mais uma automação publicada com uma etapa Enviar e-mail |
| Props passadas para o componente | Campos do payload do gatilho, usados como tags de mesclagem, como `{{ firstName \| there }}` ou `{{ trigger.planName }}` |
| O domínio do `from` | Um domínio de envio verificado no Brew |
| A sua tabela de usuários | Contatos, criados pela API REST ou por uma integração |
| Um cron job que envia uma sequência | Uma automação com etapas Aguardar, Filtro e Enviar e-mail |

### 1. Configure o domínio de envio

O agente chama `create_domain` para obter os registros DNS e depois `verify_domain`, quando os registros estiverem publicados. Domínios criados pelo MCP e pela API são domínios de marketing. Para redefinições de senha e recibos, configure no app do Brew um domínio com finalidade transacional: ele dispensa o rodapé de descadastro e entrega para contatos descadastrados. A documentação explica os registros em [como verificar o seu domínio de envio](https://docs.brew.new/get-started/verify-your-sending-domain).

### 2. Traga os contatos

O MCP atualiza contatos, mas não os cria. Crie-os a partir do seu back-end com `POST /v1/contacts`, que cria ou atualiza um contato ou um lote de até 1.000 e cria os campos personalizados automaticamente, ou faça upload de um CSV no app. Disparar um gatilho também cria ou atualiza o contato que está no payload. Só importe pessoas que concordaram em receber os seus e-mails. A página sobre [como adicionar contatos](https://docs.brew.new/audience/add-contacts) explica o que o Brew espera.

### 3. Troque os valores de prévia por tags de mesclagem

É fácil deixar passar: o e-mail de boas-vindas importado diz literalmente “Olá, Ana”. Substitua cada valor de exemplo por uma tag de mesclagem, como `{{ firstName | there }}`, em que o texto depois da barra é o valor padrão. Use o editor do Brew ou peça ao agente que rode o `edit_email` limitado a essas substituições. É uma edição feita por IA, que consome créditos, então confira a prévia. A página sobre [tags de mesclagem](https://docs.brew.new/create-emails/merge-tags) traz a sintaxe.

### 4. Crie o gatilho e a automação

O agente cria um gatilho com `save_trigger`. O esquema do payload precisa incluir o `email` do destinatário. Dê ao gatilho um contrato tipado (o `infer_payload_contract` rascunha um a partir de um payload de exemplo, e o `set_trigger_contract` o salva) e depois busque-o com `get_trigger_contract` no formato `ts` ou `zod`, para que o seu back-end e o Brew concordem sobre o formato do payload.

Em seguida, o `save_automation` monta o gatilho mais uma etapa Enviar e-mail, com o e-mail, o domínio e o assunto. A automação continua como rascunho até você publicar. O `dryRun` valida o grafo, o `test_automation` envia uma cópia real para o seu endereço e o `check_trigger` confirma que uma automação publicada está escutando antes que a produção dispare qualquer coisa.

### 5. Troque a chamada no código

Esta é a única mudança no código da sua aplicação. Antes, com o Resend:

```ts
await resend.emails.send({
  from: 'Acme <hello@acme.com>',
  to: user.email,
  subject: 'Welcome to Acme',
  react: <WelcomeEmail firstName={user.firstName} planName={plan.name} />,
})
```

Depois, com o Brew:

```ts
const res = await fetch(
  `https://brew.new/api/v1/automations/triggers/${process.env.BREW_WELCOME_TRIGGER_ID}/fire`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.BREW_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `welcome-${user.id}`,
    },
    body: JSON.stringify({
      payload: { email: user.email, firstName: user.firstName, planName: plan.name },
    }),
  }
)

if (!res.ok) {
  // 422 means the automation is still a draft; 400 means the payload failed the schema.
  throw new Error(`Brew trigger failed: ${res.status} ${await res.text()}`)
}
```

O `fetch` resolve mesmo quando a requisição é rejeitada, então verifique `res.ok` e deixe o seu executor de tarefas tentar de novo ou disparar um alerta. O endpoint retorna `202` quando aceita o evento e `422` se nenhuma automação publicada estiver escutando o gatilho.

Monte a `Idempotency-Key` a partir de um ID estável, como o do usuário ou o do pedido. Uma chave repetida retorna a execução original em vez de enviar duas vezes, e reutilizar uma chave com um corpo diferente gera `409 IDEMPOTENCY_CONFLICT`. O Brew guarda a chave de um disparo pelo mesmo tempo que o registro de recebimento do gatilho, ou seja, 90 dias, e não as 24 horas que muitas APIs usam. Trate as chaves como permanentes, uma por ação. Uma chave baseada em timestamp muda a cada nova tentativa, e uma chave reutilizada semanas depois para outra ação pode devolver a execução anterior em vez de gerar um envio.

O código de produção chama esse endpoint com uma chave de API, não pelo MCP. Via OAuth, o MCP pede que uma pessoa confirme envios reais, disparos de gatilho e publicações: certo para um chat, errado para um handler de requisições. O [guia MCP ou REST](/pt-br/blog/mcp-vs-rest-api-for-email-marketing-which-workflow-should-you-use) explica essa divisão.

### 6. Faça a virada um e-mail por vez

1. Coloque a nova chamada atrás de uma feature flag, ao lado da antiga, para que voltar atrás seja só uma mudança de configuração.
2. Comece por um e-mail de baixo risco, como um lembrete de ciclo de vida, e acompanhe as entregas e os cliques por alguns dias.
3. Depois, migre as sequências, quando os gatilhos delas estiverem disparando com os payloads certos.
4. Migre o e-mail transacional por último. Os usuários são os primeiros a notar redefinições de senha e links mágicos quebrados.
5. Apague o caminho antigo depois de uma semana sem problemas e atualize o `EMAILS.md`.

O [guia de migração do Loops](https://docs.brew.new/migrations/loops) segue o mesmo padrão.

### O que deve continuar no código

Os e-mails de confirmação e de link mágico do Supabase continuam saindo do Supabase. A integração do Brew com o Supabase escuta eventos de usuários para iniciar automações, em vez de substituir os e-mails do Supabase Auth. Uma fatura detalhada, montada a partir de dados complexos no momento do envio, também pode ser mais simples de manter no código, então confira antes o que as suas tags de mesclagem precisam. Mantenha cópias importadas no canvas para que esses e-mails continuem visíveis.

## Passo 3: otimização

**Audite o conjunto inteiro.** Rode o `audit_email` em um grupo, corrija e rode de novo antes de envios grandes.

**Confira em clientes reais.** O `test_email_rendering` retorna capturas de tela de clientes de e-mail e dispositivos específicos, inclusive no modo escuro, por 10 créditos por execução. O `create_inbox_placement_test` envia uma cópia real para caixas de teste e informa onde ela chegou, também por 10 créditos.

**Deixe o conjunto consistente.** Corrija as inconsistências no editor ou peça ao agente que aplique uma mudança, como um rodapé novo, a um grupo inteiro com `edit_email`.

**Leia os resultados.** O `get_email_analytics` informa envios e engajamento. O `list_insights` retorna os insights que o Brew detectou na sua conta, cada um com as métricas que o embasam.

**Teste variantes de forma deliberada.** Crie variantes no canvas e direcione os contatos com uma etapa Divisão baseada em comportamento, por exemplo se eles clicaram. O Brew não escolhe para você o vencedor de uma divisão aleatória, então compare as variantes você mesmo.

**Mantenha os novos e-mails visíveis.** Acrescente uma linha ao `AGENTS.md` ou a um arquivo parecido: novos e-mails do produto são criados no Brew, disparados por gatilho e adicionados ao `EMAILS.md` no mesmo pull request.

## Perguntas frequentes

### O Brew consegue importar modelos do React Email?

Sim. O `import_email` aceita HTML renderizado, MJML, uma mensagem `.eml` salva ou um módulo do React Email. O HTML renderizado é o mais confiável, porque o módulo é lido de forma estática: as props viram os valores padrão ou de prévia, e componentes importados de outros arquivos locais ficam de fora.

### O Brew se conecta à minha conta do Resend?

Não. Não há integração, importação nem exportação com o Resend. Você importa o HTML que o seu código renderiza e, se migrar, o seu código dispara um gatilho do Brew em vez de chamar o Resend.

### O meu back-end deve chamar o Brew pelo MCP?

Não. Use o MCP a partir de um agente de programação para configuração, importações, auditorias e testes. O código de produção deve chamar a API REST com uma chave de API, porque, nas conexões OAuth, o MCP pede que uma pessoa confirme envios reais e disparos de gatilho.

### Importar um e-mail muda a aparência dele?

A importação converte o e-mail em um design editável do Brew e mantém o HTML original para o envio. Ela remove scripts, iframes, formulários e manipuladores de eventos, neutraliza os pixels de rastreamento que já existiam e hospeda de novo as imagens públicas. Revise cada e-mail no canvas, nas visualizações de desktop e de celular, antes de enviá-lo pelo Brew.
