# Emails en el código: hazlos visibles, mígralos y mejóralos

URL canónica: https://brew.new/es/blog/emails-in-code-visibility-migration-mcp
Autoría: Thomas Park
Publicado: 2026-10-12
Actualizado: 2026-10-15

Los emails en el código son emails de producto definidos en tu base de código y que se lanzan como cualquier otra funcionalidad. En un equipo que trabaja con Resend y React Email, pregunta qué emails envía el producto y verás cómo se hace el silencio. El email de bienvenida es un componente en `emails/`. El de restablecimiento de contraseña vive en el panel del proveedor de autenticación. El de fin de la prueba es una cadena de texto en una tarea cron. El recibo lo envía Stripe. Y nadie recuerda qué envía el email de “te echamos de menos”.

Cada email fue fácil de lanzar, así que nadie tiene la visión de conjunto. Nosotros lo arreglaríamos por orden: primero la visibilidad, después la migración y, por último, la mejora. El agente de programación que ya usas puede hacer casi todo, conectado a Brew por MCP.

## Puntos clave

- Empieza por un inventario, no por una herramienta nueva: cada email, su disparador, su archivo y sus variables.
- Un agente de programación puede encontrar las llamadas de envío, renderizar cada plantilla e importarla con la herramienta MCP `import_email`. La importación es determinista, no usa IA y es gratuita.
- Los emails importados llegan a un lienzo por grupo, así que el equipo los revisa uno al lado del otro en escritorio y en móvil.
- Para migrar, un envío al estilo de Resend se convierte en un disparador más una automatización publicada, que se activa con una sola llamada REST. El código de producción usa REST, no MCP.
- Traslada un tipo de email cada vez detrás de un flag, y deja el email transaccional para el final.

## Por qué los emails definidos en el código se vuelven invisibles

React Email y Resend mejoraron muchísimo el email para los desarrolladores: componentes tipados y revisados en pull requests. El costo aparece más tarde.

**Los emails están dispersos.** Componentes, HTML en línea dentro de una tarea en segundo plano, plantillas en el panel de un tercero, emails que otro servicio envía por ti. Ningún archivo los recoge todos.

**Las vistas previas no son producción.** El servidor de vistas previas de React Email muestra una plantilla con sus props de vista previa, no lo que recibió un cliente, y nada de las demás plantillas.

**Solo el equipo de ingeniería puede verlos.** Producto, diseño y soporte tendrían que pedirle a alguien de ingeniería que ejecute el proyecto, así que casi nunca lo hacen.

**Nada se revisa después del lanzamiento.** El email de hace dos años sigue con el logo antiguo y un plan que ya no vendes. Nadie lo nota porque nadie los ve juntos.

## Paso 1: Visibilidad

El objetivo es una vista compartida de todos los emails. Nada de esto toca producción.

![Las tres fases para sacar los emails del código: visibilidad, con import_email y un lienzo por grupo; migración, con dominios, contactos, disparadores y automatizaciones; y optimización, con auditorías, pruebas de renderizado y analíticas.](/images/blog/emails-in-code-three-phases.png)

*Cada fase es útil por sí sola, y puedes detenerte después de cualquiera de ellas.*

### Conecta tu agente de programación a Brew

El servidor MCP de Brew está en `https://brew.new/api/mcp` y usa OAuth: inicias sesión con Brew y eliges la marca en la que puede trabajar el agente. En Claude Code:

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

El comando solo guarda el servidor en la configuración local de este proyecto. Para usarlo en todos los proyectos, añade `--scope user`. Ejecuta `claude mcp login brew`, o abre `/mcp` y autentica la conexión de Brew, antes de usar sus herramientas.

La [documentación para configurar clientes](https://docs.brew.new/api-reference/mcp/connect-your-client) incluye apartados para Cursor y Codex. Los clientes sin OAuth pueden usar una clave de API limitada a la marca. Después, pide al agente que llame a `get_brew_capabilities` para que lea la lista actual de herramientas en lugar de adivinarla.

### Crea el inventario

Pide al agente una lista:

```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.
```

Conserva la sección “Unknown” (lo que no se pudo identificar). Las confirmaciones de autenticación y los recibos de Stripe no son llamadas de envío de tu código, pero forman parte de la lista. Haz commit de `EMAILS.md`. Si te detienes aquí, ya tienes una lista que ayer no tenías.

### Renderiza cada plantilla con datos realistas

El mejor material para importar es el HTML que tu código ya produce. La [utilidad render](https://react.email/docs/utilities/render) de React Email te da esa cadena:

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

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

Los proyectos más antiguos importan `render` desde `@react-email/render`. Usa datos de prueba realistas, no `"test"`, porque tu equipo va a revisar estos emails. Pide al agente un script que renderice cada plantilla en un archivo.

Brew también importa directamente un módulo de React Email con `format: "jsx"`: lo lee de forma estática, sin ejecutarlo. Las props toman sus valores predeterminados o los de `PreviewProps`, los componentes importados desde otros archivos locales se descartan o se rechazan, y `await` y otras construcciones similares no se aceptan. Si no se trata de un único archivo autónomo, usa el HTML renderizado.

### Importa todo en un solo lugar

Después, importa cada email renderizado con `import_email`, con un `groupName` por área del producto:

```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.
```

Los desarrolladores preguntan, con razón, si la importación reescribe sus emails. No lo hace:

- Es un compilador determinista, no un modelo de IA, y no consume créditos.
- Produce un diseño de Brew editable, mientras que el envío conserva el HTML de origen de las filas importadas.
- Vuelve a alojar las imágenes públicas, y el `assetReport` enumera las que no pudo descargar.
- Elimina scripts, iframes, formularios y controladores de eventos, y desactiva los píxeles de seguimiento de aperturas que ya hubiera.
- Cada origen puede ocupar hasta 5 MB. No admite una URL como entrada: la herramienta recibe el propio marcado, y `baseUrl` solo resuelve las rutas relativas de las imágenes.

### Revisa los emails en equipo

Cada grupo tiene su lienzo. Abre `brew.new/emails/onboarding` y verás toda la secuencia lado a lado, con un selector entre la vista de escritorio y la vista móvil, y comentarios en cada diseño. Los enlaces al lienzo funcionan para quienes han iniciado sesión en tu espacio de trabajo de Brew. No hay un enlace público para compartir, así que invita a las personas que vayan a revisar.

Aquí es donde salen los problemas: dos logos, un botón con un enlace roto, tres pies de página. Ejecuta `audit_email` antes de corregir nada. Revisa enlaces, imágenes, cumplimiento normativo, tamaño total cargado, compatibilidad con los clientes de correo, accesibilidad y textos; cuesta 5 créditos por auditoría completa y acepta un `emailId` guardado o HTML sin procesar, así que puedes auditar emails que no has importado. Nuestra [checklist de control de calidad (QA) previa al envío](/es/blog/pre-send-email-qa-checklist) explica cada comprobación y cada umbral.

Ya tienes un inventario, todos los emails en un lienzo y una lista de problemas, y tu código sigue enviándolo todo como antes.

## Paso 2: Migración

Ahora Brew envía algunos emails en lugar de tu código. No hace falta trasladar todos los emails, y cada tipo de email lo envía un único sistema en cada momento.

### Equivalencias entre conceptos

| En tu código actual | En Brew |
| --- | --- |
| `resend.emails.send({ react: <WelcomeEmail /> })` | Un evento disparador más una automatización publicada con un paso Enviar email |
| Las props que se pasan al componente | Campos del payload del disparador, que se usan como etiquetas de combinación, como `{{ firstName \| there }}` o `{{ trigger.planName }}` |
| El dominio del `from` | Un dominio de envío verificado en Brew |
| Tu tabla de usuarios | Contactos, creados a través de la API REST o de una integración |
| Una tarea cron que envía una secuencia | Una automatización con pasos Espera, Filtro y Enviar email |

### 1. Configura el dominio de envío

El agente llama a `create_domain` para obtener los registros DNS y, cuando ya estén publicados, a `verify_domain`. Los dominios creados por MCP y por la API son dominios de marketing. Para los restablecimientos de contraseña y los recibos, configura en la aplicación de Brew un dominio con propósito transaccional: omite el pie de página de baja y entrega también a los contactos dados de baja. La documentación sobre [cómo verificar tu dominio de envío](https://docs.brew.new/get-started/verify-your-sending-domain) explica los registros.

### 2. Incorpora los contactos

MCP actualiza contactos, pero no los crea. Créalos desde tu backend con `POST /v1/contacts`, que hace un upsert de un contacto o de un lote de hasta 1000 y crea automáticamente los campos personalizados, o sube un CSV en la aplicación. Activar un disparador también crea o actualiza el contacto que va en su payload. Importa solo a personas que aceptaron recibir tus emails. La documentación sobre [cómo añadir contactos](https://docs.brew.new/audience/add-contacts) explica qué espera Brew.

### 3. Sustituye los valores de vista previa por etiquetas de combinación

Es fácil pasarlo por alto: el email de bienvenida importado dice literalmente “Hi Ana”. Sustituye cada valor de prueba por una etiqueta de combinación como `{{ firstName | there }}`, donde el texto que sigue a la barra es el valor predeterminado. Usa el editor de Brew o pide al agente que ejecute `edit_email` limitado a esas sustituciones. Es una edición con IA que consume créditos, así que revisa la vista previa. La documentación sobre [etiquetas de combinación](https://docs.brew.new/create-emails/merge-tags) recoge la sintaxis.

### 4. Crea el disparador y la automatización

El agente crea un disparador con `save_trigger`. El esquema del payload debe incluir el `email` del destinatario. Dale un contrato tipado (`infer_payload_contract` prepara uno a partir de un payload de ejemplo y `set_trigger_contract` lo guarda) y después obtenlo con `get_trigger_contract` en formato `ts` o `zod`, para que tu backend y Brew coincidan en la forma del payload.

Después, `save_automation` crea el disparador más un paso Enviar email con el email, el dominio y el asunto. Se queda como borrador hasta que la publicas. `dryRun` valida el grafo, `test_automation` envía una copia real a tu dirección y `check_trigger` confirma que hay una automatización publicada escuchando antes de que producción active nada.

### 5. Cambia la llamada en tu código

Es el único cambio en el código de tu aplicación. Antes, con 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} />,
})
```

Después, con 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()}`)
}
```

`fetch` se resuelve incluso cuando la solicitud se rechaza, así que comprueba `res.ok` y deja que tu sistema de tareas reintente o avise. El endpoint devuelve `202` cuando acepta el evento y `422` si ninguna automatización publicada escucha el disparador.

Construye la `Idempotency-Key` a partir de un ID estable, como el del usuario o el del pedido. Una clave repetida devuelve la ejecución original en lugar de enviar dos veces, y reutilizar una clave con un cuerpo distinto da `409 IDEMPOTENCY_CONFLICT`. Brew conserva la clave de cada activación tanto tiempo como el registro de recepción del disparador, que son 90 días, no las 24 horas que usan muchas API. Trata las claves como permanentes, una por acción. Una clave basada en una marca de tiempo cambia en cada reintento, y una clave reutilizada para otra acción semanas después puede tratarse como una repetición y no llegar a enviarse.

El código de producción llama a este endpoint con una clave de API, no por MCP. Con OAuth, MCP pide a una persona que confirme los envíos reales, las activaciones de disparadores y las publicaciones: está bien para un chat, pero no para un controlador de solicitudes. La [guía de MCP o REST](/es/blog/mcp-vs-rest-api-for-email-marketing-which-workflow-should-you-use) explica el reparto.

### 6. Haz el cambio email a email

1. Pon la llamada nueva detrás de un feature flag, junto a la antigua, para que volver atrás sea un simple cambio de configuración.
2. Empieza por un email de bajo riesgo, como un recordatorio de ciclo de vida, y vigila la entrega y los clics durante unos días.
3. Después, traslada las secuencias, cuando sus disparadores se activen con los payloads correctos.
4. Traslada el email transaccional al final. Los usuarios son los primeros en notar que fallan los restablecimientos de contraseña y los magic links.
5. Elimina el camino antiguo tras una semana sin incidencias y actualiza `EMAILS.md`.

La [guía de migración desde Loops](https://docs.brew.new/migrations/loops) sigue el mismo patrón.

### Qué debe quedarse en el código

Los emails de confirmación y de magic link de Supabase siguen saliendo de Supabase. La integración de Brew con Supabase escucha los eventos de usuario para iniciar automatizaciones; no sustituye los emails de Supabase Auth. Una factura detallada que se construye con datos complejos en el momento del envío también puede ser más sencilla en el código, así que comprueba primero qué necesitarían tus etiquetas de combinación. Deja copias importadas en el lienzo para que estos emails sigan a la vista.

## Paso 3: Optimización

**Audita el conjunto completo.** Ejecuta `audit_email` en todo un grupo, corrige y vuelve a ejecutarlo antes de los envíos grandes.

**Comprueba los clientes reales.** `test_email_rendering` devuelve capturas de clientes de correo y dispositivos concretos, también en modo oscuro, por 10 créditos por ejecución. `create_inbox_placement_test` envía una copia real a bandejas de entrada semilla e informa de dónde llegó, también por 10 créditos.

**Haz que el conjunto sea coherente.** Corrige las incoherencias en el editor o pide al agente que aplique un cambio, como un pie de página nuevo, a todo un grupo con `edit_email`.

**Lee los resultados.** `get_email_analytics` informa de los envíos y la interacción. `list_insights` devuelve los hallazgos que Brew ha detectado en tu cuenta, cada uno con las métricas que lo respaldan.

**Prueba variantes de forma deliberada.** Crea variantes en el lienzo y reparte los contactos con un paso División según su comportamiento, por ejemplo, si hicieron clic. Brew no elige por ti la variante ganadora de una división aleatoria, así que compáralas por tu cuenta.

**Mantén a la vista los emails nuevos.** Añade una línea a `AGENTS.md` o a un archivo similar: los nuevos emails de producto se crean en Brew, se activan mediante un disparador y se añaden a `EMAILS.md` en el mismo pull request.

## Preguntas frecuentes

### ¿Brew puede importar plantillas de React Email?

Sí. `import_email` acepta HTML renderizado, MJML, un mensaje `.eml` guardado o un módulo de React Email. El HTML renderizado es lo más fiable, porque el módulo se lee de forma estática: las props toman sus valores predeterminados o de vista previa, y los componentes importados desde otros archivos locales se quedan fuera.

### ¿Brew se conecta a mi cuenta de Resend?

No. No hay integración, importación ni exportación con Resend. Importas el HTML que renderiza tu código y, si migras, tu código activa un disparador de Brew en lugar de llamar a Resend.

### ¿Mi backend debería llamar a Brew por MCP?

No. Usa MCP desde un agente de programación para la configuración, las importaciones, las auditorías y las pruebas. El código de producción debe llamar a la API REST con una clave de API, porque en las conexiones OAuth MCP pide a una persona que confirme los envíos reales y las activaciones de disparadores.

### ¿Importar un email cambia su aspecto?

La importación convierte el email en un diseño de Brew editable y conserva el HTML de origen para el envío. Elimina scripts, iframes, formularios y controladores de eventos, desactiva los píxeles de seguimiento que ya hubiera y vuelve a alojar las imágenes públicas. Revisa cada email en el lienzo, en la vista de escritorio y en la vista móvil, antes de enviarlo desde Brew.
