Skip to content

Replace use_mailpit option by mail_catcher#6732

Open
tsokolovs wants to merge 4 commits into
cookiecutter:mainfrom
tsokolovs:add-use-mailtrap-local
Open

Replace use_mailpit option by mail_catcher#6732
tsokolovs wants to merge 4 commits into
cookiecutter:mainfrom
tsokolovs:add-use-mailtrap-local

Conversation

@tsokolovs

Copy link
Copy Markdown

Description

Adds a use_mailtrap_local option as a peer alternative to the existing use_mailpit prompt. Same docker-compose drop-in pattern; different tool choice for developers who prefer Mailtrap Local. Setting use_mailtrap_local=y wires Mailtrap Local into docker-compose.local.yml and the local Django email settings the same way use_mailpit=y does today (SMTP on 3535, web UI on 3550).

Checklist:

  • I've made sure that tests are updated accordingly (especially if adding or updating a template option)
  • I've updated the documentation or confirm that my change doesn't require any updates

Rationale

This adds a peer local-email-testing option; it does not change the existing Mailpit default. Mailtrap Local is an MIT-licensed, single self-contained Go binary that catches local SMTP and exposes a web UI + REST API — the same drop-in role Mailpit fills today. It gives developers who prefer it a first-class local option while leaving Mailpit users completely unaffected. The two options are mutually exclusive (enforced in the pre-gen hook), and the docs, tests, .gitignore, and docker-compose wiring mirror the existing Mailpit setup.

Changes

  • cookiecutter.json — new use_mailtrap_local prompt (default n), peer to use_mailpit
  • hooks/pre_gen_project.py — aborts generation if both catchers are enabled
  • docker-compose.local.ymlmailtrap-local service (with a tmpfs mount so the non-root image can start) + depends_on wiring for django/celeryworker/celerybeat
  • config/settings/local.pyEMAIL_HOST/EMAIL_PORT for docker and non-docker (port 3535)
  • .gitignore — ignore the mailtrap-local binary (non-docker)
  • tests/test_cookiecutter_generation.py — supported combinations + mutual-exclusion case
  • docs + READMEs — mirror the Mailpit sections

@browniebroke

Copy link
Copy Markdown
Member

the same drop-in role Mailpit fills today

Why make it a separate option, then?

@tsokolovs

Copy link
Copy Markdown
Author

the same drop-in role Mailpit fills today

Why make it a separate option, then?

From my point of view, to provide the least amount of friction (keeping it backwards-compatible) and follow existing conventions of having to pick one tool. I don't mind refactoring this into a single option, but that would be a minor breaking change for existing configs that set use_mailpit: y.

@browniebroke

Copy link
Copy Markdown
Member

We've done these kind of changes before so if we add mailtrap, I would combine it into a single option to choose between mailpit and mailtrap. If it doesn't make sense to pick them together, so let's make it impossible in the questionnaire to pick them...

I'm saying if, because I'm not sure it's worth introducing another choice the user has to make... Folks using this template may not have heard of neither mailpit nor mailtrap, so at the point of prompting they would have to potentially go learn what each does and decide which one to pick if they want this kind of solution. Right now, they only have to decide if they want local SMTP or not.

IMO this is an area where we should be opiniated and pick the best solution of the 2, based on feature easy to use and licensing. If they are equal in all these areas, the status quo wins. If the current solution isn't maintained, then that's an incentive to switch.

Offering more options or choices always adds complexity: longer CI run, more cognitive overhead on the users, etc...

Thats my 2 cents, I'm not convinced about this at the moment.

@tsokolovs

Copy link
Copy Markdown
Author

I've made the change so that there is one choice for mail_catcher (None / Mailpit / Mailtrap Local), defaulting to None, so you can't pick both by construction.

IMO this is an area where we should be opiniated and pick the best solution of the 2, based on feature easy to use and licensing. If they are equal in all these areas, the status quo wins. If the current solution isn't maintained, then that's an incentive to switch.

Is this additional choice worthwhile? I'd say it's more of the "what does the team already have" than "which one is better". There are multiple options of the "this tool is better than the other" type, e.g., mail_service, cloud provider, CI and in those cases, it's explicitly stated what the default is. Local catcher seems to be in the same boat – teams that are already running Mailtrap somewhere may find it easier to use the same catcher in their dev environment. But as the default is None, people who don't need local SMTP just leave it there and forget about it, as the choice matters only to people who have already decided to use the catcher.

Right now, both of them might be similar on features and licensing — but that's kind of my point: when two tools are comparable, 'which is objectively best' stops being the useful question, and 'which does the team already run' takes over. That's the same reasoning behind offering a choice for mail_service or CI rather than hard-coding one.

@browniebroke

Copy link
Copy Markdown
Member

I've made the change so that there is one choice for mail_catcher (None / Mailpit / Mailtrap Local), defaulting to None, so you can't pick both by construction.

Thanks, that looks better.

teams that are already running Mailtrap somewhere may find it easier to use the same catcher in their dev environment.

I see, that's a fair reasoning, when taking into consideration advanced users who know what they want: experienced devs, or established teams. I was more thinking about less experienced folks (in general or just newer to the Django ecosystem) coming to cookiecutter-django without knowing that ahead of time.

That's the same reasoning behind offering a choice for mail_service or CI rather than hard-coding one.

Yes, I think the difference with these other examples is that I see them as much more important to justify the additional friction of front-loading this choice via an option... For mail catcher, I don't think the tool matters much but perhaps it's because I don't use them myself 🤷🏻

But as the default is None, people who don't need local SMTP just leave it there and forget about it, as the choice matters only to people who have already decided to use the catcher.

Another good point, this is pretty much how I would use it. Also, as we already have an option for that, we "just" trading one for another, so it's not as bad as if we were adding an entirely new one.

Comment on lines 216 to +234
Mailpit
~~~~~~~

When developing locally you can go with Mailpit_ for email testing provided ``use_mailpit`` was set to ``y`` on setup. To proceed,
When developing locally you can go with Mailpit_ for email testing provided ``mail_catcher`` was set to ``Mailpit`` on setup. To proceed,

#. make sure ``<project_slug>_local_mailpit`` container is up and running;

#. open up ``http://127.0.0.1:8025``.

.. _Mailpit: https://github.com/axllent/mailpit/

Mailtrap Local
~~~~~~~~~~~~~~

When developing locally you can go with `Mailtrap Local`_ for email testing provided ``mail_catcher`` was set to ``Mailtrap Local`` on setup. To proceed,

#. make sure ``<project_slug>_local_mailtrap`` container is up and running;

#. open up ``http://127.0.0.1:3550``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we group these 2 under a "Mail catcher" section? Same for the local dev without Docker

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense and aligns with overall changes in this PR. Grouped under Mail catcher.

@browniebroke browniebroke changed the title Add use_mailtrap_local option as a peer to use_mailpit Replace use_mailpit option by mail_catcher Jul 17, 2026
@tsokolovs
tsokolovs force-pushed the add-use-mailtrap-local branch from c50a732 to 043064a Compare July 17, 2026 12:42
Adds a use_mailtrap_local cookiecutter prompt as a peer alternative to the
existing use_mailpit prompt (not a replacement). Setting it to y wires
Mailtrap Local into docker-compose.local.yml and the local Django email
settings the same way use_mailpit does today (SMTP on 3535, web UI on 3550).

The two options are mutually exclusive: the pre-generation hook aborts if
both are enabled. Docs, README, .gitignore and the generation test matrix
are updated to mirror the existing Mailpit setup.
The mailtrap-local image runs as a non-root user and writes its SQLite
database and secret key under /var/lib/mailtrap-local, a directory that
does not exist in the image. Without a writable mount there the container
fails to start with "unable to open database file". A tmpfs mount is
writable by the non-root user and keeps the service ephemeral, matching
the volume-less Mailpit setup.
… option

Replace the two mutually-exclusive booleans with one choice-list option
(None / Mailpit / Mailtrap Local), defaulting to None. This keeps local
SMTP opt-in as before, makes picking both impossible by construction, and
removes the pre-gen mutual-exclusivity check that is no longer needed.
Nest the Mailpit and Mailtrap Local subsections under a Mail catcher
heading in both the local and Docker local-development docs, matching
the single mail_catcher option.
@tsokolovs
tsokolovs force-pushed the add-use-mailtrap-local branch from 043064a to 19d401a Compare July 18, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants