big-mailer
big-mailer 📬
Broadcasts, Drip-Sequenzen und Transaktions-E-Mails in einem einzigen Cloudflare Worker. Ein selbst gehosteter Ersatz für einen kostenpflichtigen ESP, bei dem die Liste, der Versand und die Interaktionsdaten bei dir bleiben.
Status: funktional vollständig und lokal lauffähig, noch nicht deployed. Es ist für einen einzelnen Betreiber gebaut und nicht mandantenfähig – bewusst so entworfen, nicht aus Versehen. Siehe Vor dem Deployment für die ehrliche Liste dessen, was noch fehlt.

Das Dashboard nach dem Einspielen der Demo-Daten. Consent, nach Scope ist das Panel, das zählt: Zwei Personen haben eine einzelne Serie verlassen und sind weiterhin auf der Liste. Bei einem normalen ESP wären diese beiden Zahlen identisch.
Die Idee 💡
Jeder ESP behandelt Abmeldung als einen einzigen Schalter. Jemand beendet deine Onboarding-Serie, klickt auf „Abmelden", um die zu stoppen, und verlässt still deinen Newsletter für immer. Du erfährst es nie. Die Zahl geht einfach runter.
Hier ist Consent scoping-basiert. Eine Serie zu verlassen nimmt dich von genau dieser Serie. Den Newsletter zu verlassen, bricht keine Serie ab, in die du dich bewusst eingewählt hast. Nur eine explizite „von allem abmelden"-Aktion, ein harter Bounce oder eine Spam-Beschwerde entfernt jemanden vollständig.
Diese Asymmetrie ist der Grund, warum es das hier gibt, und alles andere im Codebase ist so angeordnet, dass sie nicht versehentlich kaputtgehen kann.
Related MCP server: Resend MCP Server
Ausführen 🚀
bun install
bun run db:migrate # applies migrations to the local D1 database
bun run dev # http://localhost:8787Öffne http://localhost:8787 und klicke auf Demo-Daten einspielen: zwölf Personen, zwei aktive Serien, ein gesendeter Broadcast. Öffne dann den Postausgang, um die E-Mails zu lesen, die „rausgegangen" sind.
Nichts verlässt deinen Rechner. EMAIL_PROVIDER=console ist die lokale Standardeinstellung und
schreibt vollständig gerenderte E-Mails in den In-App-Postausgang, statt sie zu versenden. Kein
API-Key nötig und keine Möglichkeit, versehentlich eine echte Person zu mailen, während du
herumprobierst.
Das ausprobieren, wofür es gedacht ist 🎯
Abonnenten → jemanden auswählen → Deren Präferenzzentrum öffnen
Füge
?scope=sequence:2an diese URL an. So sieht ein Link innerhalb einer Sequenz-E-Mail aus.Klicke auf Nur diese Serie stoppen
Gehe zurück zur Abonnenten-Seite: immer noch
active, immer noch im Newsletter, aus genau einer Serie rausConsent zeigt alle, die eine einzelne Serie verlassen haben, im Vergleich zur (leeren) Liste der Personen, die komplett weg sind
Sende danach einen Broadcast, und sie werden ihn trotzdem erhalten. Das ist das gesamte Argument.
Sequenz-Verzögerungen sind in Tagen, und der erste Schritt ist standardmäßig 0 (kommt beim Beitritt an), während spätere Schritte standardmäßig 1 sind. Das bedeutet, eine eingespielte Serie wird nicht fertig, während du zusiehst – deshalb hat das Dashboard Uhr vorspulen (nur lokal): Es zieht jeden ausstehenden Schritt auf jetzt und führt einen Tick aus. Nutze es, und du siehst, wie Schritt 2 die Personen überspringt, die diese Serie verlassen haben.
Was hier drin ist 🗂
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 logRund 16.000 Zeilen TypeScript. bun run typecheck deckt den Worker und das Browser-Bundle
getrennt ab und ist sauber.
Architektur auf einen Blick 🧱
Cloudflare Workers · D1 (SQLite) über Drizzle · Queues für den Send-Fan-out · Cron-Trigger
für die Terminierung · R2 für Medien · Hono + JSX serverseitig gerendertes Admin · Cloudflare Access
für Auth · ein pluggbarer EmailProvider-Port mit console- und Resend-Adaptern.
Die Entscheidungen, die es wert sind, bekannt zu sein, und warum:
Jede messages-Zeile wird materialisiert, bevor auch nur ein Versand rausgeht. Ein Broadcast
löst seine vollständige Empfängerliste im Voraus auf, schreibt eine Zeile pro beabsichtigtem Versand
und verteilt erst dann an die Queue. Das macht einen Broadcast nach einem Absturz fortsetzbar,
idempotent über Queue-Wiederholungen hinweg und im Nachhinein prüfbar. Empfänger erst zum
Sendezeitpunkt lazy aufzulösen ist billiger und verwandelt jeden Fehler mitten im Broadcast in ein
nicht wiederherstellbares Chaos.
Consent wird unmittelbar vor dem Provider-Aufruf erneut geprüft, nicht beim Einreihen in die Queue. Eine Queue kann Minuten nach der Erstellung der Nachricht zustellen, und jemand kann zwischenzeitlich opt-outen. Beim Einreihen zu prüfen würde sie trotzdem mailen.
Die Queue-Konkurrenz ist auf 6 festgepinnt. Ein Batch ist eine Provider-Anfrage, also ist Batch-Konkurrenz die Anfragenrate. Wenn sie nicht gesetzt ist, skaliert Cloudflare Queues auf 250 gleichzeitige Consumer hoch, begräbt Resends 10 req/s-Limit unter 429ern, verbrennt alle drei Wiederholungen und dead-lettert einwandfreie E-Mails. Sechs Batches à 100 lassen ~600 E-Mails/Sek. Spielraum, während sie unter dem Limit bleiben.
Die Unterdrückung ist nach E-Mail-Adresse geschlüsselt, nicht nach Abonnent. Transaktions-
Empfänger und gebouncte Adressen haben oft gar keine Abonnenten-Zeile, also würde ein
subscribers.status-Flag sie still übersehen.
Alles Beobachtbare ist eine Zeile in D1, niemals eine Log-Zeile. Worker-Logs laufen nach 3–7
Tagen ab. Ein Audit-Trail mit einer Woche Aufbewahrung ist keiner. mcp_calls zeichnet jede
Agenten-Aktion auf, einschließlich der Ablehnungen; sync_runs zeichnet jeden Stripe-Abzug auf.
Die Admin-Konsole hat kein Passwort. Cloudflare Access beendet die Identität an der Edge, und
src/web/auth.ts verifiziert das weitergeleitete JWT ordentlich: Signatur gegen das Live-JWKS des
Teams (pro Isolate gecacht, mit erzwungenem Neufetch bei unbekannter Key-ID), alg auf RS256
festgepinnt, plus Audience, Issuer, exp und nbf. Das Vorhandensein des Headers beweist nichts
und wird nie als Beweis behandelt. Falsch konfiguriert, failt die Middleware geschlossen und
sperrt alle aus, auch dich. Das ist die richtige Fail-Richtung.
Der E-Mail-HTML-Renderer ist handgeschrieben (core/render-doc.ts) statt @tiptap/html zu
verwenden, dessen Server-Einstiegspunkt happy-dom braucht und nicht in workerd läuft. Es stellte
sich ohnehin als die bessere Antwort heraus: Der Walker inline-t jeden Stil (Gmail entfernt
<style>) und erzeugt verschachtelte Tabellen für Buttons (Outlook ignoriert Padding auf <a>),
was generische HTML-Serialisierung nicht tun würde.
Das vollständige Entscheidungsprotokoll, einschließlich der abgelehnten Alternativen und warum,
steht in docs/MEMORY.md.
Consent-Modell 🔐
Drei unabhängige Scopes. Eine enge Wahl eskaliert nie zu einer weiten.
Scope | Gespeichert als | Wirkung |
Sequenz |
| Weg von genau dieser Serie. Alles andere läuft weiter. |
Broadcast |
| Weg vom Newsletter. Serien laufen weiter. |
Global |
| Weg von allem. Die rechtliche Notluke. |
Nur eine explizite „von allem abmelden"-Aktion, ein harter Bounce oder eine Beschwerde schreibt eine globale Unterdrückung.
Sequenz-Sends ignorieren bewusst status = 'unsubscribed', weil dieses Flag Broadcast-scoping hat:
Jemand, der den Newsletter verlassen hat, bekommt trotzdem die Onboarding-Serie, die er angefordert
hat. Transaktions-E-Mails (Belege, Downloads) ignorieren Marketing-Consent vollständig und werden
nur durch eine tote Adresse oder eine Spam-Beschwerde blockiert. Ein Beleg ist kein Marketing, und
ein abgemeldeter Kunde braucht trotzdem seinen Download.
Der Editor ✍️
Blockbasierter Rich-Text, TipTap v3, vanilla (kein React). Öffne den eingespielten Entwurf „Entwurf: alles, was der Editor kann", um das Ganze zu sehen.
/in einer Zeile → Blockmenü: Überschriften, Listen, Checkliste, Zitat, Code, Tabelle, Toggle, Trennlinie, Bild, YouTube, CTA-Button@→ Personalisierungsfelder als echte Nodes, damitfirst_namenicht falsch geschrieben werden kannZiehe den Griff am linken Rand zum Umsortieren; Umschalt-Auswahl markiert mehrere Blöcke
Ziehe ein Bild per Drag-and-Drop hinein oder füge es ein → Upload zu R2, Einfügen, sobald die URL zurückkommt
Codeblöcke sind syntax-hervorgehoben (15 Sprachen, inkl. Ruby, Elixir, TS, SQL)
Text für das Bubble-Menü auswählen; einen Button auswählen und das Bubble-Menü wird zu dessen URL- und Farbwähler
Buttons und Merge-Tags sind benutzerdefinierte Nodes, die speziell für E-Mail gebaut wurden.
Ein CTA rendert als verschachtelte Tabelle, und jeder Stil ist inline. Ein Merge-Tag ist eine Node
statt rohem {{first_name}}-Text, weil ein Tippfehler im Rohtext „Hi {{frist_name}}" an die
gesamte Liste mailt.
Inhalte werden als TipTap-JSON in body_json gespeichert. Legacy-Markdown in body_md rendert
weiterhin und wird konvertiert, sobald du es im Editor öffnest. Es wird nichts in großen Mengen
migriert, weil eine Massenmigration, die schiefgeht, das Archiv mitnimmt.
Das Client-Bundle ist ~226 KB gzipped und lädt nur auf den beiden Bildschirmen, die E-Mail verfassen. Alles andere in der Admin-Konsole ist serverseitig gerendert mit null JavaScript.
Es gibt einen Browser-Smoke-Test (bun run smoke, 33 Prüfungen), der echtes Chromium fährt,
weil eine umbenannte Erweiterungsoption im Browser still scheitert und das Body-Feld einfach nie
speichert. Nichts Serverseitiges kann das abfangen.
Von Claude Code aus steuern 🤖
Der Worker bedient einen MCP-Server unter POST /mcp/<secret>: 93 Tools, die den gesamten
Mailer abdecken, sodass ein Agent Segmente schneiden, Broadcasts entwerfen und senden, Sequenzen
bauen, Kampagnen-Performance lesen und Stripe abgleichen kann.
# 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"Drei Tore, das billigste zuerst. Ein unerratbares Pfad-Geheimnis, das in konstanter Zeit
verglichen wird (ein Fehlschlag gibt 404 zurück, nicht 403, weil eine URL, die niemand erraten
hat, aussehen sollte, als wäre da nichts), dann ein admin-scoped Bearer-Key (transaktionale
send-Keys kommen nicht daran), dann Per-Tool-Guards. Jeder Aufruf landet in mcp_calls,
einschließlich der Ablehnungen.
Irreversible Sends brauchen einen Preflight. broadcast_send weigert sich ohne ein Token von
broadcast_preflight: einmal verwendbar, 10 Minuten Ablauf, ungültig gemacht durch jede Änderung am
Inhalt oder an der Zielgruppe. Dasselbe für sequence_activate. Obendrein ist MCP_ALLOW_SEND in
Produktion "false", sodass MCP alles lesen und entwerfen kann, aber keine E-Mail auf die Leitung
bringen kann, bis du es bewusst umlegst. Es zurückzulegen ist der sofortige Ausschalter.
Agenten sollten bigmailer://conventions lesen, bevor sie Consent anfassen. Scoped-Unsubscribe ist
nicht die Form, die irgendetwas erwartet, das auf normalen ESPs trainiert wurde, und es falsch zu
machen ist genau das Versagen, das dieses Projekt verhindern sollte.
Stripe → Kampagnen-Attribution 💳
Setze STRIPE_SECRET_KEY (eingeschränkt, nur lesend auf charges/refunds/customers). Ein täglicher
Cron um 09:17 UTC zieht neue Charges und schreibt jede dem letzten Attribution-Touch des Käufers
gut, oder metadata.campaign, wenn die Charge eine trägt. Idempotent auf der Stripe-Charge-ID,
sodass Wiederholungsläufe und überlappende Backfills harmlos sind.
stripe_sync_preview führt einen Trockenlauf aus, sales_unattributed ist die Arbeitsliste dessen,
was die Heuristik nicht zuordnen konnte, und sync_runs_list beweist, dass der nächtliche Job
tatsächlich läuft.
Befehle ▶️
| Client-Bundle bauen, dann auf :8787 ausliefern |
| Editor-Bundle bei Änderung neu bauen (parallel zu |
| Browser-Smoke-Test des Editors. Braucht laufendes |
| Migrationen auf lokales D1 anwenden |
| Migration nach Bearbeitung von |
| Drizzle Studio gegen die lokale Datenbank |
| Typprüfung des Workers und des Browser-Bundles getrennt |
| Bauen, dann |
Wirklich senden 📮
Kopiere .dev.vars.example nach .dev.vars, füge einen Resend-Schlüssel hinzu und setze
EMAIL_PROVIDER=resend. Richte den Webhook von Resend auf /webhooks/resend ein, damit Bounces und
Beschwerden korrekt unterdrückt werden. Ohne diesen Webhook werden ungültige Adressen nie unterdrückt
und dein Senderuf verschlechtert sich stillschweigend – das ist der langsame Weg, die Zustellbarkeit
für die gesamte Domain zu verlieren.
Geheimnisse gehören in .dev.vars (gitignored) oder per wrangler secret put. Niemals in
wrangler.jsonc und niemals in .dev.vars.example.
⚠️ Vor dem Deployment
Noch nicht deployed, und zwischen hier und live gibt es echte Einrichtungsschritte:
DEV_AUTH_BYPASS=truesteht in den Vars der obersten Ebene vonwrangler.jsonc, damit die App lokal ausführbar ist.--env productionsetzt es auf false. Ein nackteswrangler deployveröffentlicht eine unauthentifizierte Admin-Konsole, weshalbbun run deployfest--env productionverwendet. Arbeite nicht dagegen.database_idist ein Platzhalter. Erstelle eine echte D1-Datenbank mitwrangler d1 create.Erstelle den R2-Bucket (
big-mailer-media) und die beiden Queues (big-mailer-send,big-mailer-dlq). Queues benötigen den kostenpflichtigen Workers-Plan für 5 $/Monat.Setze
PUBLIC_URLauf den echten Host. Er wird in Tracking-Links und Bild-URLs zum Zeitpunkt des Sendens eingebacken, sodass ein falscher Wert dauerhaft kaputte E-Mails an bereits zugestellte Nachrichten liefert. Das kann im Nachhinein nicht korrigiert werden.MCP_PATH_SECRETmuss ein echtes Geheimnis sein (wrangler secret put), keine Var. Wenn nicht gesetzt, antwortet der MCP-Endpunkt mit 404, was der sichere Standard ist. Aktiviere es bewusst.Cloudflare Access benötigt eine Allow-App und mehrere Bypass-Apps. Wenn du den gesamten Hostnamen mit einer einzigen Allow-Richtlinie schützt, schützt das auch den Tracking-Pixel, die Anmeldeformulare, das Präferenzzentrum, die Webhooks und MCP, was bedeutet, dass jeder Tracking-Pixel in jeder E-Mail, die du sendest, dauerhaft zu einem Anmeldebildschirm umleitet – für bereits zugestellte Nachrichten. Access matcht zuerst den spezifischsten Pfad, daher benötigen
/t,/f,/p,/api,/webhooksund/mcpjeweils ihre eigene Bypass-App. Jede davon trägt ihre eigene Authentifizierung oder ist von Natur aus öffentlich.Keine serverseitige Testsuite.
bun run smokedeckt den Editor ab.docs/SPEC.mdist als nummerierte, testbare Anforderungen geschrieben, die so gestaltet sind, dass sie ausführbar gemacht werden können.
Dokumentation 📚
Das Problem, für wen es ist und was explizit ausgeschlossen ist | |
Systemdesign, Datenmodell und die Sendepipeline | |
Nummerierte Verhaltensanforderungen. Die Referenz für das beabsichtigte Verhalten | |
Entscheidungslog: Was gewählt wurde, was verworfen wurde und warum |
Mitwirken 🤝
Fehlerberichte, Korrektheitskorrekturen und Korrekturen für E-Mail-Client-Rendering sind sehr willkommen.
Multi-Tenancy, ein Drag-and-Drop-Builder und selbst betriebenes SMTP sind bewusst ausgeschlossen.
Siehe CONTRIBUTING.md, bevor du einen PR eröffnest, und
SECURITY.md, wenn du eine Schwachstelle gefunden hast (bitte melde sie
privat, nicht als Issue).
Forking ist ausdrücklich erwünscht. Das Projekt ist klein genug, um es vollständig zu lesen und dein eigenes zu machen. Die Teilnahme unterliegt dem Code of Conduct.
Lizenz 📄
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