Skip to content

feat: catch outgoing email with Mailpit (kiqr open mail)#29

Merged
kjellberg merged 2 commits into
kiqr:mainfrom
kjellbergzoey:feat/mailpit-email-catcher
Jun 8, 2026
Merged

feat: catch outgoing email with Mailpit (kiqr open mail)#29
kjellberg merged 2 commits into
kiqr:mainfrom
kjellbergzoey:feat/mailpit-email-catcher

Conversation

@kjellbergzoey

Copy link
Copy Markdown
Contributor

What

Adds an agent-level Mailpit email catcher so outgoing mail from any kiqr project is captured locally instead of being delivered for real, plus a kiqr open mail shortcut to view it.

Agent compose (src/lib/agent.ts)

  • New mailpit service using axllent/mailpit:v1.30.1 (tag verified to exist via the Docker Hub registry API), container_name: kiqr-mailpit, on KIQR_NETWORK, restart: unless-stopped.
  • Traefik labels mirror the existing splash pattern: Host(\mail.lvh.me`)on entrypointweb, loadbalancer server port 8025(Mailpit's web UI). Mailpit also listens for SMTP on1025` for in-network clients.
  • kiqr-mailpit is added to AGENT_CONTAINERS, so it shows up in kiqr agent status.

WordPress mail routing (src/lib/mu-plugin.ts)

  • Adds a phpmailer_init hook that points PHPMailer at SMTP host kiqr-mailpit, port 1025, no auth, no encryption. The WordPress container shares KIQR_NETWORK, so it resolves kiqr-mailpit by name.
  • Existing auto-login + LiveReload behavior is unchanged.

kiqr open mail (src/commands/open.tsx)

  • New mail / mailpit target that opens http://mail.lvh.me:5477, following the existing open-target pattern.

Applying the change

Because this touches both the agent stack and the per-project mu-plugin, after pulling this in you need to:

  • kiqr agent restart — to bring up the new kiqr-mailpit container and Traefik route.
  • kiqr restart (per project) — to re-write the mu-plugin so WordPress starts routing mail to Mailpit.

Tests

  • Agent compose: asserts the mailpit service image, kiqr-mailpit container name, network, restart policy, and the Traefik Host/entrypoint/port labels.
  • mu-plugin: asserts the phpmailer_init SMTP routing hook (isSMTP, host kiqr-mailpit, port 1025, SMTPAuth=false).

All four gates pass locally: typecheck, test (210 passed), build, lint.

Not verified

  • Live SMTP capture was not verified — there is no Docker daemon in this environment, so I could not actually boot Mailpit + WordPress and confirm a real email lands in the Mailpit inbox. Generation logic is covered by unit tests; the end-to-end SMTP path (DNS resolution of kiqr-mailpit, Traefik routing of mail.lvh.me, PHPMailer delivery) was validated only by inspection.

Docs

README documentation for kiqr open mail and the mail catcher will follow — a separate README rewrite PR is currently open, so this PR intentionally leaves README.md untouched. cc @kjellberg

🤖 Generated with Claude Code

Add an agent-level Mailpit email catcher so outgoing mail from any kiqr
project is captured instead of being sent for real.

- Add a `mailpit` service (axllent/mailpit:v1.30.1) to the kiqr agent
  compose, on KIQR_NETWORK with container_name kiqr-mailpit. Traefik
  routes Host(`mail.lvh.me`) to the Mailpit web UI on port 8025. SMTP
  is served on 1025 for in-network clients. restart: unless-stopped,
  and the container is included in getAgentStatus via AGENT_CONTAINERS.
- Route WordPress mail to Mailpit by adding a `phpmailer_init` hook to
  the per-project mu-plugin: SMTP to host kiqr-mailpit:1025, no auth,
  no encryption. Auto-login and LiveReload behavior are unchanged.
- Add a `mail`/`mailpit` target to `kiqr open` that opens
  http://mail.lvh.me:5477.
- Tests assert the compose mailpit service (image/labels/port), the
  mu-plugin SMTP routing hook, and existing agent-status coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kjellbergzoey
kjellbergzoey force-pushed the feat/mailpit-email-catcher branch from a2d42a7 to c2ab1a3 Compare June 8, 2026 20:00
@kjellbergzoey

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (resolved conflicts in mu-plugin.ts + its test against the tunnel/share work — kept main's redirect-canonical + dynamic-URL handling and slotted the Mailpit phpmailer_init SMTP block alongside it; all test cases retained). My files are lint-clean, 227 tests pass, build green.

Heads-up unrelated to this PR: main itself currently fails npm run lint (5 files: up.tsx, restart.tsx, share.ts, BitnamiRuntimeProvider.ts, share.test.ts) — looks like the tunnel/Apache change was pushed to main without biome format. So this PR's lint check will be red until main is fixed, but none of those files are touched here. 🤖

The tunnel/share + Apache-conf work landed on main un-formatted, failing
`npm run lint`. Formats up.tsx, restart.tsx, share.ts,
BitnamiRuntimeProvider.ts, and share.test.ts. Pure formatting, no behavior
changes. Folded into this PR so its CI lint check (and main, on merge) goes
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kjellberg
kjellberg merged commit ab4fb68 into kiqr:main Jun 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants