# Flask-Mail: SMTP sending for Flask applications

> Flask-Mail is a small Flask extension that turns a set of MAIL_ configuration keys and a Message object into mail sent through an SMTP server, maintained as part of the Pallets community ecosystem. It suits applications that already speak SMTP, and it is the wrong choice for a product whose mail goes through a transactional provider API.

**pallets-eco/flask-mail** — Flask-Mail adds SMTP mail sending to your Flask applications

- Repository: https://github.com/pallets-eco/flask-mail
- Website: https://flask-mail.readthedocs.io
- Stars: 643 · Forks: 176
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/pallets-eco-flask-mail

## What Flask-Mail actually is: a thin, typed extension over SMTP

Flask-Mail is an extension for Flask that makes it easy to send emails from your application, and the README is explicit about the ambition: it simplifies the process of integrating email functionality, allowing you to focus on building features for your application.

That framing sets the expectation correctly. This is not a queueing system, a template engine, a bounce handler or a deliverability toolkit. It is the layer that turns a message plus a configuration block into an SMTP conversation, and it does that in a small amount of code.

The shape of the package follows the Flask extension convention. The distribution is named Flask-Mail and the import is flask_mail, which is what the flit module configuration in pyproject.toml declares. The runtime dependency list is short, containing flask and blinker, and there is no email-queue broker, no HTTP client and no template engine in it, which means the extension inherits Flask's own mail facilities and adds the configuration and message layer.

The metadata is worth reading for what it signals about the state of the project. Version 0.10.0, Development Status :: 4 - Beta, Framework :: Flask, License :: OSI Approved :: BSD License, and Typing :: Typed. That last classifier is the notable one, because it is backed by configuration rather than aspiration: the project runs mypy in strict mode over src/flask_mail and tests, runs pyright in basic mode over the same paths, and marks the package as typed for consumers.

The declared floor is requires-python >= 3.9, while the type checking configuration targets Python 3.12, which is a common and sensible split: the package still installs on older interpreters, and the tooling uses a current one.

## Configuration: the MAIL_ keys and what they decide

Everything the extension needs is expressed as Flask configuration, and the README's example shows the full set it expects a developer to fill in: MAIL_SERVER for the host, MAIL_PORT for the port, MAIL_USE_TLS and MAIL_USE_SSL as the two transport switches, MAIL_USERNAME and MAIL_PASSWORD for credentials, and MAIL_DEFAULT_SENDER for the envelope sender used when a message does not name one.

The example's values are the interesting part, because they show a working configuration rather than a placeholder list. The port is 587 with MAIL_USE_TLS set to true and MAIL_USE_SSL set to false, which is the ordinary arrangement for submission over an encrypted connection to a relay, and MAIL_DEFAULT_SENDER is set to an address on the sending domain so that replies land somewhere sensible.

Two of those settings carry real consequences. MAIL_USERNAME and MAIL_PASSWORD are the relay credentials, and they sit in the application configuration, which in a deployed application means they belong in environment variables or a secret store rather than in a committed config file. And MAIL_DEFAULT_SENDER is the address that SPF and DKIM alignment depend on, so setting it to a domain you do not control is a common way to have otherwise correct mail land in spam.

The README does not enumerate the rest of the supported keys or document how the TLS and SSL switches interact, so the documentation site at flask-mail.readthedocs.io is where the complete list belongs. What the example establishes is that there is a small, fixed set of settings and no per-message configuration file.

## The example: constructing a Message and sending it

The README's simple example is short enough to read in one pass, and it exercises every part of the public API you need for a first integration:

```python
from flask import Flask
from flask_mail import Mail, Message

app = Flask(__name__)
app.config['MAIL_SERVER'] = 'your_mail_server'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USE_SSL'] = False
app.config['MAIL_USERNAME'] = 'your_username'
app.config['MAIL_PASSWORD'] = 'your_password'
app.config['MAIL_DEFAULT_SENDER'] = 'your_email@example.com'

mail = Mail(app)

@app.route('/')
def send_email():
  msg = Message(
    'Hello',
    recipients=['recipient@example.com'],
    body='This is a test email sent from Flask-Mail!'
  )
  mail.send(msg)
  return 'Email sent succesfully!'
```

Four things happen here and each one is the API. The application is created and its mail settings are written into app.config, following the Flask pattern of configuration by dictionary rather than by constructor arguments. The extension is instantiated once with Mail(app), which binds it to the application in the standard Flask way, so the mail object is a single instance shared by the whole app. A Message is constructed with a subject, a list of recipients and a body. And mail.send(msg) performs the delivery.

The Message constructor takes the subject as its first positional argument and keyword arguments for the rest, with body as the plain-text alternative to an HTML body. Because the extension is constructed once with the app, sending happens through that object, which is where the configuration is read from.

What the example does not show is what a production version needs: a background worker so that a slow SMTP relay cannot stall a request thread, a retry policy, and a template. None of those are in the package, which is the boundary of what this extension does.

## Inside the repository: tests, typing and a deliberate release cadence

The repository is small and organised, and its configuration tells you what the maintainers care about. Pytest is configured with testpaths pointing at tests and filterwarnings set to error, so any warning raised during the test run fails the run rather than scrolling past. Coverage is configured with branch coverage enabled over the flask_mail package and the tests, and paths are mapped so that a source checkout and an installed package report as the same files.

Type checking is done twice and differently on purpose. Mypy runs in strict mode with error codes shown and pretty output, over src/flask_mail and tests, and Pyright runs in basic mode over the same paths, both targeting Python 3.12. Running two checkers catches different classes of mistake, and the mypy configuration even carries an override to ignore missing imports for one third-party module.

The developer tooling is modern in a specific way. Dependencies are managed with uv, with a uv.lock committed, the build backend is flit_core through the flit module configuration, and there are dependency groups for build, dev, docs, tests, typing and a gha-update group that only applies on interpreters above 3.12. Pre-commit is configured in the repository root, and the tox and tox-uv packages are in the dev group.

The docs are Sphinx-based, with furo as the theme, myst-parser for the markup, and sphinxcontrib-log-cabinet, which is the extension Pallets projects use to render changelog entries as a version history in the documentation. The documentation site is flask-mail.readthedocs.io and the changelog lives at the changes page of the docs, with CHANGES.md in the repository root.

The release history is the one thin spot. The only published release is 0.10.0 on 2024-05-23, and the last push to the repository was on 2026-06-10. The project is not archived, so work continues, but there is no stream of tagged versions to point at.

## Governance: how Flask-Mail is maintained

The README carries a notice about the Pallets Community Ecosystem, and it explains the arrangement better than most projects do. Pallets is described as the open source organisation that maintains Flask, and Pallets-Eco as the mechanism that enables community maintenance of related projects. The notice invites people who want to help maintain the project to reach out on the Pallets Discord server.

That is a governance answer to a real problem. Flask-Mail was originally written by Dan Jacob, who is still listed as the author in the package metadata, while the maintainer field now names the Pallets Ecosystem with contact@palletsprojects.com. The project has moved from one person to an organisation with a channel for contributors, and the repository has the usual Pallets furniture: a code of conduct, a contributing guide, a changelog, an editorconfig and a pre-commit configuration.

For an adopter, the practical meaning is that maintenance does not depend on a single person's spare time, but that the release cadence is slower than a commercially maintained library. The last release predates the last push by more than a year, which suggests a package that is stable rather than abandoned, and it also means you should read the changelog before upgrading, because a fix may only exist on the branch.

The licence is BSD-3-Clause, with the text in LICENSE.txt, and it is referenced from pyproject.toml by file rather than by an SPDX string. That is permissive, with the customary obligation to keep the notice, and there is no copyleft obligation that would affect your application.

## Where Flask-Mail is the wrong choice

The first wrong case is a transactional email provider used through its API. Modern providers deliver over HTTP with their own retry, suppression and bounce handling, and a service that talks SMTP is an extra hop with an extra failure mode. Flask-Mail is built around an SMTP conversation, and if your provider gives you only an HTTP API, this extension is the wrong shape for the job.

The second wrong case is sending mail from a request handler at volume. The example sends inside a route, synchronously. For anything beyond a handful of messages per minute, you want the send to happen off the request, and this package does not include a queue, a worker or a retry policy, so you have to add them yourself.

The third wrong case is a dependency chain you cannot justify. The extension pulls in flask and blinker, and the project documents its own history as living alongside the Pallets family of extensions, so there may be a more specific package for your stack, or a different solution entirely, depending on what else you already run.

The fourth is a team that needs a maintained release stream. One release, 0.10.0 from 2024-05-23, and a branch that moved in June 2026, is a thin history for a dependency you intend to keep for years.

The alternative that remains sensible for many projects is to skip the extension and use Python's own smtplib and email modules directly, or an async mail client, wiring the connection into your application's own configuration and retry logic. The difference in approach is that you own the SMTP conversation rather than delegating it, which costs a small amount of code but removes a dependency and leaves you free to swap in a provider API later without changing your call sites.

## Conclusion

Flask-Mail earns its place in a Flask application that already has an SMTP relay, where the setup is a few configuration keys and one send call, and where the package's typed, strictly checked, well tested codebase matters to a team that treats dependencies as code. It does not fit an application that sends through a transactional provider's HTTP API, a project that needs per-recipient tracking and templating at scale, or a team that wants a maintained release stream, since the only published release is 0.10.0 from 2024-05-23. Verify first that your SMTP server accepts the port and TLS combination you configure, that your MAIL_PASSWORD credential matches the mechanism your relay requires, and that the extension still works with the Flask version your project has resolved.

## FAQ

### What is the package name and import for Flask-Mail?

The distribution is Flask-Mail, as declared in pyproject.toml, and the import is flask_mail, taken from the flit module configuration and the README example which uses from flask_mail import Mail, Message.

### Which configuration keys does Flask-Mail need?

The README example sets MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS, MAIL_USE_SSL, MAIL_USERNAME, MAIL_PASSWORD and MAIL_DEFAULT_SENDER, which covers the relay host, port, transport switches, credentials and the default sender address.

### How do I send an email from a Flask view with Flask-Mail?

Construct a Message with a subject, recipients and a body, then call mail.send(msg) on the Mail instance that was initialised with your application. The README example does exactly that inside a route.

### Who maintains Flask-Mail now?

The README places the project in the Pallets Community Ecosystem, where Pallets-Eco enables community maintenance of related projects, and the package metadata lists the Pallets Ecosystem as maintainer with contact@palletsprojects.com. Dan Jacob remains the listed author.

### Which Python versions does Flask-Mail support?

The package requires Python 3.9 or later, and the type checking configuration for mypy and pyright targets Python 3.12. The package is also marked as typed for consumers.

### What licence is Flask-Mail released under?

It is released under the BSD-3-Clause licence, with the text in the LICENSE.txt file referenced from pyproject.toml, and the classifiers list it as OSI Approved :: BSD License.

## Sources

- [License: BSD-3-Clause](https://github.com/pallets-eco/flask-mail/blob/main/LICENSE)
- [pallets-eco/flask-mail on GitHub](https://github.com/pallets-eco/flask-mail)
- [Project website](https://flask-mail.readthedocs.io)
- [README](https://github.com/pallets-eco/flask-mail/blob/main/README.md)
- [Releases](https://github.com/pallets-eco/flask-mail/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pallets-eco-flask-mail
