Handoff: Sip N Serve email platform (Mautic/Listmonk on Resend)¶
URL: https://mkdocs.justinsforge.com/memory/handoffs/sipnserve-email-platform-2026-07-01/
Date: 2026-07-01 For: sipnserve-email worker (Fable 5) Directive from Justin (hard): Do NOT keep suggesting we give up and switch to a SaaS (Klaviyo/MailerLite). Problem-solve and make the self-hosted solution work. He got frustrated when the parent kept offering the SaaS escape hatch. Also: keep momentum, don't propose pausing/wrapping (see feedback_no_stopping_suggestions).
Goal¶
Give Krystal a self-hosted, self-serve newsletter tool for Sip N Serve Society (Austin volleyball community) that produces good-looking emails without Justin/Claude hand-coding each one. Delivery is owned via Resend. This is a pilot; if it works, extend to the other 3 brands (Nova, Gus, JustinWieb) — those need Resend Pro ($20/mo) since free tier = 1 domain (deferred, Justin's spend call).
Read for full detail¶
forge/memory/daily/2026-07-01.md— session checkpoints (search "Listmonk multi-brand email platform BUILT" and later).forge/memory/plans/multi-brand-email-listmonk-resend-2026-07-01.md— the 16-task plan.
Infrastructure (all live)¶
- forge-mail = dedicated LXC CT 112, 192.168.86.87 (Debian 13, Docker), created on Finn for this.
ssh [email protected]. 4GB RAM / 4 cores. - Listmonk v6.2:
/opt/listmonkcompose, port 9000,lists.justinsforge.com. Repo:forge/infra/listmonk/. Basic editor — likely being retired in favor of Mautic. - Mautic 5.2.11:
/opt/mauticcompose (db/web/cron/worker), port 8090,mautic.justinsforge.com. Repo:forge/infra/mautic/. This is the front-runner (real GrapesJS drag-drop builder + asset library). - Cloudflare tunnel rides media-server's cloudflared (tunnel id a55b7768-b42a-4d78-82b5-4a427bdbb9b7, account 5b4138008d635b79e07af0c7388cd46f). Access apps gate Listmonk
/admin+/apiand Mautic/s; public paths (unsubscribe, tracking) stay open. Allowlist: [email protected], [email protected], [email protected]. OTP idp. - Resend: sender for everything. Keys in
~/.forge-secrets/resend.env(RESEND_API_KEYfull,RESEND_SMTP_KEYsending).sipnservesociety.comverified (DKIM+SPF). SMTP smtp.resend.com:587 userresend. - Secrets:
~/.forge-secrets/{resend,listmonk,mautic,cloudflare}.env(chmod 600). Mautic adminadmin/pw + DB creds in mautic.env; Listmonk adminjustin/pw in listmonk.env. NOTE: Listmonk v6 API-token auth never worked → all Listmonk data changes were done via direct Postgres inserts.
Mautic state (the important part)¶
- Branded
sipnservetheme (MJML, GrapesJS-editable) installed; email id=1 "Sip N Serve — Test Newsletter" built on it. Itscustom_htmlrenders CLEAN MJML (cobalt #0137f4 header + wordmark logo + intro + shaded event card with blue "Reserve your spot" button + footer). Confirmed clean (0 CKEditor<figure>junk). - The jank Justin saw was NOT the theme — it was using the wrong send path: contact → "Send Email" → "import from existing template" dumps the design into Mautic's CKEditor and mangles it (figure-wrapped tables, default margins, ugly mailjet social icons). The clean path is a Segment email sent to a segment.
- A background agent (Mautic deploy agent) is CURRENTLY wiring: make email id=1 a segment email (
email_type='list'), create "Sip N Serve Members" segment (lead_lists) with contact [email protected] (lead_lists_leads), link email→segment (email_list_xref), fix the mailjet dark-square social icons → mailchimp CDN icons, and fire a verified test send to the segment. CHECK THAT AGENT'S RESULT FIRST before redoing anything. Mautic tokens:{unsubscribe_text},{webview_text}. - Logo currently hotlinks Mailchimp CDN (
mcusercontent.com/.../69f8650a-...png) — should be self-hosted (tech debt).
Design DNA (Sip N Serve)¶
Cobalt blue #0137f4, cream #ecefe6, white canvas, dark text #1a1a2e, shaded card #f7f8fb, warm lowercase voice. Socials: Instagram (instagram.com/sipnservesociety), website. Reference design: forge/infra/listmonk/templates/sip-n-serve.html.
Next steps (updated 2026-07-01 evening, worker session)¶
- DONE. Segment send verified end-to-end twice: sync path 19:41Z and async doctrine-queue path 19:44Z, both landed in Gmail INBOX (not Promotions),
email_statsclean, 0 failed. - DONE. Logo + both social icons self-hosted at
https://mautic.justinsforge.com/media/images/(public through tunnel, no Access block). Rewroteemails.custom_html,bundle_grapesjsbuilder.custom_mjml(builder source — do not miss this table when editing email HTML by hand), theme twigs on forge-mail, and repo copies incl.email-compiled.html. Images also checked intoforge/infra/mautic/media/images/. Justin should still eyeball the builder once. - DONE. Krystal login: user
krystal/ password in~/.forge-secrets/mautic.env(MAUTIC_KRYSTAL_PASSWORD), role "Sip Editor" (emails/assets/contacts/segments/categories full; 403 on user/config admin — verified). Created via Mautic API enabled temporarily with basic auth, then disabled again (verified 403 api_disabled). She's already on the Cloudflare Access allowlist. - FIXED en route: worker container was crash-looping (
messenger:consumevs defaultsync://transport, hundreds of restarts/min). Compose now setsMAUTIC_MESSENGER_DSN_EMAIL/HIT=doctrine://default(forge-mail + repo copy); worker consumes cleanly, sends flow queue→worker→Resend. - OPEN: Listmonk vs Mautic call is Justin's; recommendation is Mautic (theme, builder, segment flow, roles all proven; Listmonk v6 API auth never worked). On approval retire Listmonk container + repo dir.
- OPEN: Commit
forge/infra/{listmonk,mautic}+ plan + handoff (Justin hasn't approved the commit yet — ask). Branch, don't commit to main directly. - Later (needs Resend Pro): onboard Nova/Gus/JustinWieb brands, DMARC hardening.
Gotchas¶
- context-mode hook BLOCKS curl/wget/WebFetch on Console; run curl only remotely via ssh, use WebSearch for docs, python urllib works with a browser User-Agent (Resend API is behind Cloudflare, blocks python-urllib default UA — set
User-Agent: curl/8.5.0). - Mautic needs
trusted_proxies=['0.0.0.0/0','::/0']in local.php (already set) or it infinite-redirects through the tunnel.