Skip to content

Added an optional Mailpit for local sites - #351

Merged
acburdine merged 1 commit into
next-dockerfrom
claude/mailpit-ghost-docker-local-9f993e
Oct 8, 2026
Merged

acburdine merged 1 commit into
next-dockerfrom
claude/mailpit-ghost-docker-local-9f993e

Conversation

@acburdine

Copy link
Copy Markdown
Member

closes https://linear.app/ghost/issue/PLA-502/optional-mailpit-for-local-sites-install-local-with-mailpit

A local site has no mail configured, so staff invites, password resets and member sign-in links go nowhere. ./ghost-docker install --local --with mailpit runs Mailpit beside Ghost and points Ghost's SMTP settings at it, so that mail lands in a web inbox on 127.0.0.1.

  • Opt-in, local only. --domain with --with mailpit exits 2; config validate rejects mailpit beside production in COMPOSE_PROFILES, where it would swallow real mail.
  • Ghost's settings go in ghost.env, not compose.yml's environment, which would override an operator's or an import's real SMTP. They are the operator's afterwards.
  • Per site. The inbox port is chosen like Ghost's (from 8025, skipping published and host-held ports); messages are kept in data/mailpit. A non-empty data/mailpit is refused like the other data directories.
  • Import. install --import --with mailpit is allowed, and mailpit is the only optional service an import takes. The source's mail__transport and mail__options__* are dropped (named, without values) and Mailpit's written instead; mail__from is kept. A local copy of a real site then never mails real members.
  • Verification. install and check require Mailpit's health check and its SMTP greeting at mailpit-<project>:1025 from inside the ghost container. The summary and info print the inbox URL.
  • labs__publicAPI is now added for ActivityPub only, rather than for any optional service.

Newsletters (including "send test email") go through Ghost's Mailgun provider, not SMTP, so Mailpit does not see them; the docs say so.

Tests

  • pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm test: 277 pass.
  • tests/e2e/install.sh: passes on OrbStack. New step: install with Mailpit, set up the owner, request a password reset, and find the mail in Mailpit's API, before and after a restart. The existing port-conflict step was skipped for this run (port held in advance): it fails on OrbStack independently of this change, because the manager now refuses a host-held --port before Docker can; that test is being fixed separately.
  • tests/e2e/import.sh: passes. New step: import with --with mailpit, check the replaced transport and kept mail__from, and catch the imported site's password-reset mail.

🤖 Generated with Claude Code

closes https://linear.app/ghost/issue/PLA-502/optional-mailpit-for-local-sites-install-local-with-mailpit

A local site has no mail configured, so Ghost cannot deliver staff
invites, password resets or member sign-in links, and sign-in links are
what theme development needs most. `install --local --with mailpit` now
runs Mailpit beside Ghost and points Ghost's SMTP settings at it, so
that mail lands in a web inbox on the loopback interface.

It is opt-in and local only. In production it would quietly swallow the
site's real mail, so `--domain` with it is a usage error and validation
rejects the profile beside `production`. Ghost's settings are written
into ghost.env rather than set by compose.yml, because container-owned
keys would override an operator's or an import's real SMTP; the lines
are the operator's afterwards. Each site gets its own Mailpit, with its
inbox port chosen the way Ghost's is and its messages kept in
data/mailpit.

An import may take it too, and only it: a local copy of a real site
then sends nothing to real members. The source's mail transport is
dropped and named, and its mail__from kept. Newsletters go through
Mailgun's API, not SMTP, so Mailpit never sees them; the docs say so.

- compose.yml: the mailpit service, pinned by digest, under its own
  profile, with a readiness health check and a unique network alias.
- manager/src/site.ts, config.ts, undo.ts: the profile, the local-only
  rule, and clean-up of a failed install's Mailpit.
- manager/src/commands/install.ts: --with mailpit, its port, ghost.env
  settings, data directory and summary line. labs__publicAPI is now
  set for ActivityPub only, not for any optional service.
- manager/src/import.ts, import/config.ts: --with mailpit on import,
  replacing the source's mail transport.
- manager/src/verify.ts, commands/site.ts: check and install verify
  Mailpit's health and its SMTP greeting from the ghost container;
  info prints the inbox URL.
- tests/e2e/install.sh, import.sh: a password reset from Ghost arrives
  in Mailpit, for a new site and an imported one; the inbox survives a
  restart.
- docs/install.md, configuration.md, bundle-v1.md, .env.example,
  AGENTS.md: documented.
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 0e7bea42-e70a-4979-99ae-df8c71ae055d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@acburdine
acburdine merged commit 41cfc3c into next-docker Oct 8, 2026
7 checks passed
@acburdine
acburdine deleted the claude/mailpit-ghost-docker-local-9f993e branch October 8, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant