big-mailer
big-mailer 📬
Boletines, secuencias de goteo y correo transaccional en un solo Cloudflare Worker. Un sustituto autoalojado de un ESP de pago, donde la lista, el envío y los datos de interacción siguen siendo tuyos.
Estado: funcionalmente completo y ejecutable en local, aún no desplegado. Está construido para un operador único y no es multiinquilino, por diseño y no por omisión. Consulta Antes de desplegar para ver la lista honesta de lo que queda.

El panel después de sembrar los datos de demostración. Consentimiento, por ámbito es el panel que importa: dos personas abandonaron una serie individual y siguen en la lista. En un ESP normal esos dos números son el mismo número.
La idea 💡
Todo ESP trata la baja como un único interruptor. Alguien termina tu serie de incorporación, hace clic en "darse de baja" para detener eso, y desaparece silenciosamente de tu boletín para siempre. Nunca te enteras. El número simplemente baja.
Aquí, el consentimiento está delimitado por ámbito. Abandonar una secuencia te quita de esa secuencia. Abandonar el boletín no cancela una serie en la que te apuntaste deliberadamente. Solo una baja explícita de "todo", un rebote duro o una queja por spam elimina a alguien por completo.
Esa asimetría es la razón de que esto exista, y todo lo demás en el código está organizado para que no se pueda romper por accidente.
Related MCP server: Resend MCP Server
Ejecútalo 🚀
bun install
bun run db:migrate # applies migrations to the local D1 database
bun run dev # http://localhost:8787Abre http://localhost:8787 y haz clic en Sembrar datos de demostración: doce personas, dos series activas, un boletín enviado. Luego abre la Bandeja de salida para leer el correo que "salió".
Nada sale de tu máquina. EMAIL_PROVIDER=console es el valor predeterminado local y escribe
correo completamente renderizado en la Bandeja de salida de la aplicación en lugar de enviarlo. No se necesita clave API, y
no hay forma de enviar correo accidentalmente a una persona real mientras experimentas.
Prueba aquello para lo que está hecho 🎯
Suscriptores → elige a alguien → Abre su centro de preferencias
Añade
?scope=sequence:2a esa URL. Así es como se ve un enlace dentro de un correo de secuencia.Pulsa Detener solo esta serie
Vuelve a su página de suscriptor: sigue
active, sigue en el boletín, fuera de exactamente una serieConsentimiento muestra a todos los que abandonaron una sola serie frente a la lista (vacía) de personas que se fueron por completo
Envía un boletín después y aun así lo recibirán. Ese es todo el argumento.
Los retrasos de secuencia están en días, y el primer paso tiene como valor predeterminado 0 (llega al unirse) mientras que los pasos posteriores tienen como valor predeterminado 1. Lo que significa que una serie sembrada no terminará mientras la observas, así que el panel tiene Adelantar el reloj (solo local): mueve cada paso pendiente al momento actual y ejecuta un tick. Úsalo y verás cómo el paso 2 se salta a las personas que abandonaron esa serie.
Qué hay aquí 🗂
src/
worker.tsx fetch + scheduled + queue handlers — the whole entry point, 151 lines
core/ domain logic: consent, sending, sequences, segments, rendering
db/ Drizzle schema (24 tables) and the D1 client
web/ server-rendered admin console (Hono + JSX, no frontend framework)
api/ transactional send API, signup forms, media upload, bearer-key auth
mcp/ MCP server — 93 tools, 4 resources, 4 prompts
providers/ EmailProvider port + console and Resend adapters
client/ the only browser JS in the project: the TipTap editor bundle
migrations/ drizzle-kit generated, applied by wrangler
docs/ problem brief, architecture, spec, and a decision logAproximadamente 16 mil líneas de TypeScript. bun run typecheck cubre el Worker y el paquete
del navegador por separado y está limpio.
Arquitectura de un vistazo 🧱
Cloudflare Workers · D1 (SQLite) mediante Drizzle · Queues para la distribución de envíos · Cron Triggers
para la programación · R2 para medios · Hono + JSX renderizado en servidor para la administración · Cloudflare Access para
autenticación · un puerto EmailProvider conectable con adaptadores console y Resend.
Las decisiones que merece la pena conocer, y por qué:
Cada fila de messages se materializa antes de que salga un solo envío. Un boletín
resuelve su lista completa de destinatarios de antemano, escribe una fila por envío previsto, y solo
entonces se distribuye a la cola. Eso hace que un boletín sea reanudable tras un fallo, idempotente
entre reintentos de cola y auditable después. Resolver destinatarios de forma diferida en el momento del envío
es más barato y convierte cualquier fallo a mitad de boletín en un desastre irrecuperable.
El consentimiento se vuelve a comprobar inmediatamente antes de la llamada al proveedor, no en el momento de ponerlo en cola. Una cola puede entregar minutos después de que se creara el mensaje, y alguien puede darse de baja entretanto. Comprobarlo al poner en cola les enviaría el correo de todos modos.
La concurrencia de la cola está fijada en 6. Un lote es una solicitud al proveedor, así que la concurrencia de lotes es la tasa de solicitudes. Si se deja sin configurar, Cloudflare Queues escala automáticamente a 250 consumidores concurrentes, entierra el límite de 10 req/s de Resend bajo 429s, agota los tres reintentos y deja en la cola de mensajes muertos correo perfectamente válido. Seis lotes de 100 dejan ~600 correos/seg de margen mientras se permanece bajo el límite.
La supresión se basa en la dirección de correo, no en el suscriptor. Los destinatarios transaccionales
y las direcciones rebotadas a menudo no tienen ninguna fila de suscriptor, así que un indicador subscribers.status
pasaría por alto silenciosamente.
Cualquier cosa observable es una fila en D1, nunca una línea de registro. Los registros de Workers caducan en 3–7
días. Un rastro de auditoría con una retención de una semana no es tal cosa. mcp_calls registra cada
acción del agente, incluidas las denegaciones; sync_runs registra cada extracción de Stripe.
La consola de administración no tiene contraseña. Cloudflare Access termina la identidad en el
borde, y src/web/auth.ts verifica el JWT reenviado correctamente: la firma contra el
JWKS en vivo del equipo (almacenado en caché por aislado, con una recuperación forzada ante un id de clave desconocido),
alg fijado a RS256, más audiencia, emisor, exp y nbf. La presencia de la cabecera
no prueba nada y nunca se trata como prueba. Si se configura mal, el middleware
falla en modo cerrado y bloquea a todos, incluido a ti. Esa es la dirección correcta en la que fallar.
El renderizador de HTML de correo está escrito a mano (core/render-doc.ts) en lugar de usar
@tiptap/html, cuyo punto de entrada de servidor necesita happy-dom y no se ejecuta dentro de
workerd. Resultó ser la mejor respuesta de todos modos: el recorredor inserta en línea cada estilo
(Gmail elimina <style>) y emite tablas anidadas para los botones (Outlook ignora el padding
en <a>), algo que la serialización HTML genérica no haría.
El registro completo de decisiones, incluidas las alternativas que se rechazaron y por qué, está en
docs/MEMORY.md.
Modelo de consentimiento 🔐
Tres ámbitos independientes. Una elección restrictiva nunca escala a una amplia.
Ámbito | Almacenado como | Efecto |
Secuencia | fila | Fuera de esa serie. Todo lo demás continúa. |
Boletín |
| Fuera del boletín. Las series siguen activas. |
Global | fila | Fuera de todo. La vía de escape legal. |
Solo una baja explícita de "todo", un rebote duro o una queja escribe una supresión global.
Los envíos de secuencia ignoran deliberadamente status = 'unsubscribed', porque ese indicador tiene
ámbito de boletín: alguien que abandonó el boletín sigue recibiendo la serie de incorporación que
solicitó. El correo transaccional (recibos, descargas) ignora por completo el consentimiento de marketing
y solo se bloquea por una dirección muerta o una queja por spam. Un recibo no es marketing,
y un cliente dado de baja sigue necesitando su descarga.
El editor ✍️
Texto enriquecido por bloques, TipTap v3, vanilla (sin React). Abre el borrador sembrado "Borrador: todo lo que el editor puede hacer" para verlo todo.
/en una línea → menú de bloques: encabezados, listas, lista de verificación, cita, código, tabla, alternar, divisor, imagen, YouTube, botón CTA@→ campos de personalización como nodos reales, así quefirst_nameno se puede escribir malArrastra el asa en el margen izquierdo para reordenar; mayús-clic selecciona varios bloques
Suelta o pega una imagen en cualquier lugar → se sube a R2, se inserta cuando la URL regresa
Los bloques de código tienen resaltado de sintaxis (15 idiomas, incl. Ruby, Elixir, TS, SQL)
Selecciona texto para el menú de burbuja; selecciona un botón y el menú de burbuja se convierte en su URL y selector de color
Los botones y las etiquetas de combinación son nodos personalizados construidos específicamente para correo. Un CTA se renderiza
como una tabla anidada, y cada estilo se inserta en línea. Una etiqueta de combinación es un nodo en lugar de
texto {{first_name}} sin procesar, porque un error tipográfico en texto sin procesar envía "Hola {{frist_name}}" a
toda la lista.
Los cuerpos se almacenan como JSON de TipTap en body_json. El markdown heredado en body_md aún se
renderiza y se convierte en el momento en que lo abres en el editor. Nada se migra en masa,
porque una migración masiva que sale mal se lleva el archivo consigo.
El paquete del cliente tiene ~226KB comprimidos con gzip y se carga solo en las dos pantallas que componen correo. Todo lo demás en la consola de administración se renderiza en el servidor con cero JavaScript.
Hay una prueba de humo del navegador (bun run smoke, 33 comprobaciones) que maneja Chromium real,
porque una opción de extensión renombrada falla silenciosamente en el navegador y el campo del cuerpo
simplemente nunca se guarda. Nada del lado del servidor puede detectar eso.
Condúcelo desde Claude Code 🤖
El Worker sirve un servidor MCP en POST /mcp/<secret>: 93 herramientas que cubren
todo el mailer, así que un agente puede segmentar, redactar y enviar boletines, construir secuencias,
leer el rendimiento de las campañas y conciliar Stripe.
# 1. a path secret (this is what makes the endpoint exist at all)
openssl rand -hex 24 # → put in .dev.vars as MCP_PATH_SECRET
# 2. an admin-scoped key — POST /seed prints one, or use apikey_create
# 3. point Claude Code at it
claude mcp add --transport http --scope local \
--header "Authorization: Bearer $BIG_MAILER_KEY" \
big-mailer "http://localhost:8787/mcp/$MCP_PATH_SECRET"Tres puertas, la más barata primero. Un secreto de ruta imposible de adivinar comparado en tiempo constante
(un fallo devuelve 404, no 403, porque una URL que nadie adivinó debería parecer que no hay nada
ahí), luego una clave portadora con ámbito admin (las claves de send transaccional no pueden
alcanzarla), y luego guardas por herramienta. Cada llamada aterriza en mcp_calls, incluidas las denegaciones.
Los envíos irreversibles necesitan una verificación previa. broadcast_send se niega sin un token de
broadcast_preflight: de un solo uso, caducidad de 10 minutos, invalidado por cualquier edición del
contenido o de la audiencia. Igual para sequence_activate. Además, MCP_ALLOW_SEND
es "false" en producción, así que MCP puede leer y redactar todo pero no puede poner correo en
el cable hasta que lo actives deliberadamente. Volver a apagarlo es el interruptor de apagado instantáneo.
Los agentes deberían leer bigmailer://conventions antes de tocar el consentimiento. La baja
con ámbito no es la forma que espera nada entrenado con ESP normales, y hacerlo
mal es exactamente el fallo que este proyecto se construyó para evitar.
Stripe → atribución de campaña 💳
Configura STRIPE_SECRET_KEY (restringida, de solo lectura en cargos/reembolsos/clientes). Un cron
diario a las 09:17 UTC extrae los nuevos cargos y acredita cada uno al último toque de atribución
del comprador, o a metadata.campaign cuando el cargo lleva uno. Idempotente con el id de cargo
de Stripe, así que las re-ejecuciones y los rellenos superpuestos son inofensivos.
stripe_sync_preview lo prueba en seco, sales_unattributed es la lista de trabajo de lo que la
heurística no pudo ubicar, y sync_runs_list demuestra que el trabajo nocturno está
realmente ejecutándose.
Comandos ▶️
| Compila el paquete del cliente y luego sirve en :8787 |
| Recompila el paquete del editor al cambiar (junto con |
| Prueba de humo del editor en el navegador. Necesita |
| Aplica migraciones al D1 local |
| Genera una migración después de editar |
| Drizzle Studio contra la base de datos local |
| Comprueba tipos del Worker y del paquete del navegador por separado |
| Compila y luego |
Enviar de verdad 📮
Copia .dev.vars.example a .dev.vars, añade una clave de Resend y establece
EMAIL_PROVIDER=resend. Apunta el webhook de Resend a /webhooks/resend para que los
rebotes y las quejas se supriman correctamente. Sin ese webhook, las direcciones
incorrectas nunca se suprimen y tu reputación de envío se degrada silenciosamente, que
es la forma lenta de perder la entregabilidad de todo el dominio.
Los secretos van en .dev.vars (ignorado por git) o mediante wrangler secret put.
Nunca en wrangler.jsonc, y nunca en .dev.vars.example.
⚠️ Antes de desplegar
Aún no está desplegado, y hay configuración real entre aquí y la producción:
DEV_AUTH_BYPASS=truese encuentra en las variables dewrangler.jsoncde nivel superior para que la aplicación se pueda ejecutar localmente.--env productionlo establece en false. Unwrangler deploysin más publica una consola de administración sin autenticar, por esobun run deploytiene--env productioncodificado. No intentes saltártelo.database_ides un marcador de posición. Crea una base de datos D1 real conwrangler d1 create.Crea el bucket de R2 (
big-mailer-media) y las dos colas (big-mailer-send,big-mailer-dlq). Las colas necesitan el plan de pago de Workers de 5 $/mes.Establece
PUBLIC_URLal host real. Se incrusta en los enlaces de seguimiento y las URL de imágenes en el momento del envío, por lo que un valor incorrecto hace que se envíe correo permanentemente roto a mensajes ya entregados. No se puede corregir a posteriori.MCP_PATH_SECRETdebe ser un secreto real (wrangler secret put), no una variable. Si no está definido, el endpoint MCP devuelve 404, que es el comportamiento seguro por defecto. Actívalo deliberadamente.Cloudflare Access necesita una aplicación Allow y varias aplicaciones Bypass. Proteger todo el nombre de host con una única política Allow también protege el píxel de seguimiento, los formularios de registro, el centro de preferencias, los webhooks y el MCP, lo que significa que cada píxel de seguimiento de cada correo que envías redirige a una pantalla de inicio de sesión, permanentemente, para el correo ya entregado. Access aplica primero la ruta más específica, por lo que
/t,/f,/p,/api,/webhooksy/mcpnecesitan cada uno su propia aplicación Bypass. Cada una de ellas lleva su propia autenticación o es pública por diseño.No hay suite de pruebas del lado del servidor.
bun run smokecubre el editor.docs/SPEC.mdestá escrito como requisitos numerados y comprobables, diseñados para poder hacerse ejecutables.
Documentación 📚
El problema, a quién va dirigido y qué queda explícitamente fuera de alcance | |
Diseño del sistema, modelo de datos y el proceso de envío | |
Requisitos de comportamiento numerados. La referencia para el comportamiento previsto | |
Registro de decisiones: qué se eligió, qué se rechazó y por qué |
Contribuciones 🤝
Los informes de errores, las correcciones de correctitud y las correcciones de
renderizado en clientes de correo son muy bienvenidos. La multitenencia, el constructor
de arrastrar y soltar y el SMTP autogestionado están fuera de alcance a propósito.
Consulta CONTRIBUTING.md antes de abrir un PR, y
SECURITY.md si has encontrado una vulnerabilidad (por favor, repórtala
de forma privada, no como un issue).
Hacer fork está realmente recomendado. Es lo suficientemente pequeño como para leerlo de principio a fin y hacerlo tuyo. La participación está cubierta por el Código de Conducta.
Licencia 📄
MIT © Rob Conery
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseCqualityBmaintenanceMCP server that exposes the complete Libredesk REST API (54 endpoints) as tools, enabling natural language management of conversations, contacts, agents, teams, and more for the open-source customer support desk.54113MIT
- AlicenseAqualityFmaintenanceAn MCP server for the Resend email API, enabling AI assistants to send emails, manage contacts, audiences, and domains through natural language.1844MIT

xmit-mcpofficial
FlicenseNot gradedqualityDmaintenanceRemote MCP server for the Transmit email platform, enabling email sending, contact management, template and campaign operations via natural language.- FlicenseCqualityDmaintenanceComprehensive MCP server for Mailchimp Marketing API v3.0 with over 104 tools and 15+ React UI apps, enabling management of campaigns, audiences, ecommerce, automations, reports, and more via natural language.1001
Related MCP Connectors
GibsonAI MCP server: manage your databases with natural language
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Markdown-first MCP server for Notion API with 8 composite tools and 39 actions.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/robconery/big-mailer'
If you have feedback or need assistance with the MCP directory API, please join our Discord server