Kevin CMS manda correo por el binding send_email de Cloudflare Email Sending. Solo transaccional:
verificar una cuenta y restablecer una contraseña. No hay newsletters, ni listas, ni seguimiento de
aperturas, y no está previsto que los haya.
Los tres requisitos de plataforma
Sección titulada «Los tres requisitos de plataforma»Es la parte que más veces falla, y ninguna de las tres se arregla desde el código:
- Un plan Workers de pago. Email Sending no está en el gratuito.
- Un dominio que use DNS de Cloudflare, dado de alta en Compute → Email Service → Email Sending.
Cloudflare añade los MX, SPF, DKIM y DMARC del subdominio
cf-bounce. - Que
frompertenezca a ese dominio. Cualquier otro remitente se rechaza.
El mensaje de MISSING_EMAIL las enumera las tres, porque saber solo que «falta el binding» manda a
mirar al sitio equivocado dos de cada tres veces.
Configúralo
Sección titulada «Configúralo»-
El binding, en tu
wrangler.jsonc:wrangler.jsonc {"send_email": [{ "name": "EMAIL" }]} -
El remitente, en tu config:
cms.config.ts import { defineConfig } from '@kevolution-co/cms'export default defineConfig({email: {from: 'noreply@tudominio.com',siteName: 'Mi sitio',replyTo: 'hola@tudominio.com',},collections: [],})
| Clave | Por defecto | Qué hace |
|---|---|---|
from |
— | Remitente. Tiene que pertenecer al dominio dado de alta |
siteName |
'Kevin CMS' |
Firma las plantillas: aparece en el asunto y en el pie |
replyTo |
sin cabecera | Dirección de respuesta, si quieres una distinta del remitente |
El nombre del binding se cambia con bindings.email en la
integración, igual que los de D1 y R2.
En local no necesitas nada
Sección titulada «En local no necesitas nada»wrangler dev simula el envío: no manda nada a ninguna parte, escribe el mensaje en un fichero
temporal e imprime su ruta en la consola.
[wrangler:info] send_email binding called with MessageBuilder:From: noreply@example.comTo: kevin@example.comSubject: Verifica tu cuenta en Kevin CMSText: /tmp/miniflare-…/email-text/<id>.txtCon eso se desarrollan los flujos completos sin dominio, sin plan de pago y sin buzón: te das de alta, intentas iniciar sesión, abres el fichero, copias el enlace y lo sigues.
Los correos que manda
Sección titulada «Los correos que manda»| Correo | Cuándo sale |
|---|---|
| Verificación de la cuenta | En el primer intento de iniciar sesión sin verificar, no en el alta |
| Restablecimiento de contraseña | Al pedirlo con POST /api/auth/request-password-reset |
Los dos los dispara better-auth, no tu código. El de verificación se puede
reenviar con POST /api/auth/send-verification-email.
Son HTML con tablas y estilos en línea —lo único que se ve igual en todos los clientes de correo— y siempre viajan con una versión en texto plano al lado. Sin ella el mensaje puntúa peor en los filtros de spam y se ve mal en lo que no renderiza HTML. El enlace aparece tal cual, en su propia línea, dentro de esa versión, para que se pueda copiar cuando el botón no se puede pulsar.
Todo valor que se interpola en el HTML se escapa, así que un nombre con <script> llega como texto y no
como marcado.
Mandar correo tuyo
Sección titulada «Mandar correo tuyo»Astro.locals.cms.email expone el mismo emisor:
---const { email } = Astro.locals.cms
const { messageId } = await email.send({ to: 'kevin@example.com', subject: 'Gracias por escribir', html: '<p>Te respondemos en menos de 24 h.</p>', text: 'Te respondemos en menos de 24 h.',})---| Campo | |
|---|---|
to |
Una dirección o varias |
subject |
|
html |
|
text |
Obligatorio junto a html |
from |
Opcional. Por defecto el de la config |
replyTo |
Opcional. Por defecto el de la config |
Devuelve { messageId }.
Es un accesor perezoso: una página que no manda correo no resuelve el binding.
Cuando falla
Sección titulada «Cuando falla»send() no reintenta. El error sube al que llamó, que es quien sabe qué hacer con él:
- Si falla el correo de verificación, el alta no se revierte. La cuenta se queda creada y sin verificar, y el siguiente intento de iniciar sesión manda otro correo. Perder la cuenta por una caída temporal del correo sería peor.
- Si falla el de restablecimiento, el error llega al usuario, porque sin ese correo el flujo no sigue.
En ambos casos se registra un console.error con el destinatario y el asunto, y nada más. Ni el
cuerpo ni el enlace: los dos llevan un token vivo, y los logs duran bastante más que la hora que dura el
token.