fastapi-mail: sending mail from a FastAPI service
Fastapi mail system sending mails(individual, bulk) attachments(individual, bulk)
At a glance
- What is it?
- fastapi-mail is a small library that puts an email API in front of an SMTP server, aimed squarely at FastAPI services. It handles individual and bulk sends, attachments, Jinja templates, and it keeps its dependency list to asynchronous SMTP, Pydantic settings and a template engine.
- Who is it for?
- Adopt fastapi-mail if you already run FastAPI and want email sending that fits the framework, with typed settings, a template engine and background task support rather than a pile of smtplib calls in a route handler. Do not adopt it if you need a full email platform, since this is a sending library rather than a campaign manager, and check the redis extra before you rely on the background task path.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 60 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A mail library with a narrow job
fastapi-mail is a simple, lightweight mail system for FastAPI, and the summary of what it does is: send emails, attachments, and HTML templates. The repository description puts it as a mail system sending mails, individual and bulk, with attachments, individual and bulk.
The scoping matters. This is a sending library, not a platform. There is no campaign management, no subscriber list, no open tracking, no unsubscribe handling and no scheduling. What it gives you is a typed way to build a message and hand it to an SMTP server, which is exactly the gap that appears in a FastAPI service the first time somebody asks for a password reset email.
The design is idiomatic for the framework it targets. Messages are Pydantic models, the connection settings are a settings object, and the send call is awaited, so it works inside an async route without a thread pool. The templates are Jinja, which is the default in most Python web stacks, and attachments are part of the message rather than a separate step.
The package is published to PyPI as fastapi-mail, the licence is MIT, and the documentation is on GitHub Pages at sabuhish.github.io/fastapi-mail. The author is Sabuhi Shukurov, and the repository is not archived, with the last push on 2026-08-24.
Installation and the first send
Installation is one command:
pip install fastapi-mailThe quick start in the README is a complete, runnable FastAPI application, and it is worth reading as a whole because it shows the shape of the API in one screen.
from fastapi_mail import ConnectionConfig, FastMail, MessageSchema, MessageType, NameEmail
conf = ConnectionConfig(
MAIL_USERNAME="username",
MAIL_PASSWORD="**********",
MAIL_FROM="[email protected]",
MAIL_PORT=465,
MAIL_SERVER="mail server",
MAIL_STARTTLS=False,
MAIL_SSL_TLS=True,
USE_CREDENTIALS=True,
VALIDATE_CERTS=True,
)
@app.post("/email")
async def simple_send(email: EmailSchema) -> JSONResponse:
message = MessageSchema(
subject="Fastapi-Mail module",
recipients=email.model_dump().get("email"),
body="<p>Thanks for using Fastapi-mail</p>",
subtype=MessageType.html,
)
await FastMail(conf).send_message(message)
return JSONResponse(status_code=200, content={"message": "email has been sent"})Three objects do the work. ConnectionConfig holds the server settings, MessageSchema describes the message, and FastMail is the sender that takes the configuration and the message. The subtype of MessageType.html is what tells the library the body is HTML rather than plain text, and NameEmail is the typed name and address pair used for recipients, with a Pydantic model on top of it for the request body.
The example is deliberately a route handler, because that is where the library is meant to be used.
The connection settings, and the flags that matter
The configuration object is a list of uppercase fields, which is a little unusual next to the rest of the Python ecosystem, and the fields divide into three groups.
The identity and server fields are MAIL_USERNAME, MAIL_PASSWORD, MAIL_FROM, MAIL_PORT and MAIL_SERVER. The transport fields are MAIL_STARTTLS, MAIL_SSL_TLS, USE_CREDENTIALS and VALIDATE_CERTS.
The transport flags are where a deployment goes wrong, so it is worth being explicit. In the example the port is 465, MAIL_SSL_TLS is True and MAIL_STARTTLS is False, which is implicit TLS on the connection, the mode used by submission on port 465. A server that expects STARTTLS on port 587 wants the opposite combination. The two are not the same thing and the library will not pick for you, because the difference depends on what the server advertises.
USE_CREDENTIALS decides whether the username and password are offered at all, which is the switch for an internal relay that authenticates nothing. VALIDATE_CERTS decides whether the server's certificate is verified, and turning it off is the only way to talk to a server using a certificate your machine does not trust, such as one from a private certificate authority. Turning it off is a decision to make knowingly, in a specific environment, rather than a default to carry into production.
The whole object is a Pydantic settings model, since pydantic-settings is a dependency, which means the values can come from environment variables as well as from the constructor.
The dependency list is the design, and the extras
The pyproject dependency list is short enough to read as an explanation of how the library works.
starlette is there because the library is built to live inside a Starlette application, and httpx is the HTTP client it uses for the web-based sending path, along with cryptography, which is what handles the certificate material and the SOCKS or TLS setup that aiohttp and smtplib cannot do alone. pydantic and pydantic-settings provide the typed message and the settings, email-validator checks the addresses, and typing-extensions keeps the generics working on the supported Python versions.
Then the two that say most about the shape of the project. jinja2 is the template engine, which is how HTML emails with a context are rendered. And aiosmtplib is an asynchronous SMTP client, which is what lets send_message be awaited instead of blocking a worker thread while the server talks to yours.
Blinker is the last one, and it is the smallest but most telling. Blinker is a signalling library, and the reason a mail library needs it is background tasks: sending a large batch of mail should not happen inside the request that triggered it. The optional extras confirm the shape. There is an httpx extra, and there is a redis extra, with redis at 7.4.0 or later and below 8.0.0, which is a Redis-backed task queue for that background sending path.
So the library covers both the simple case and the batch case, and the redis extra is how you make the batch case survive a restart.
The examples directory shows the feature set
The examples/ directory in the repository is a better feature list than the README, because each file is a runnable piece of the API.
There is a bulk_email_example.py, which is the batch path, and a name_email_example.py, which covers the typed recipient objects. There is a schema.py, which is about the Pydantic models, and a templates.py, which is the Jinja side of HTML mail. And there is a utils_example.py, for the helper functions.
That is a sensible set, and it maps onto the questions a user actually has. How do I send to many people, how do I name my recipients, how do I define the request shape, how do I render a template, and what helpers do I get. The README's short quick start covers the fifth one implicitly, and the rest are in the examples.
The package itself is the fastapi_mail/ directory, with the tests in tests/ and the documentation in docs/ built with MkDocs through the mkdocs.yml file. The build backend is poetry-core 2.0 or later, and the project requires Python 3.10 or later, which is consistent with the async-first design and the modern typing it uses.
The development tooling is a dev dependency group with pytest, pytest-asyncio for the async tests, pytest-mock, pytest-cov, fakeredis for testing the Redis-backed path without a server, mypy, black, isort and flake8, plus mkdocs-material and the markdown extension plugin for the documentation build.
How the project is linted and tested
The Makefile is three targets, and each one is a command you would otherwise type by hand.
The lint target runs isort in diff and check mode, then black, then flake8, then mypy with --ignore-missing-imports, and the format_code target runs the first three in write mode. Every one of them excludes app.py and the virtual environment, which is the kind of detail that suggests the project grew out of a single-script application.
The test target is the one to read:
pytest -vvv --cov-report term-missing --cov-report html --cov-branch \
--cov fastapi_mail/Coverage is measured on the package directory only, with branch coverage on, and both a terminal report and an HTML report are produced. The -vvv verbosity is the sign of a suite where a failure needs the full output to diagnose, which is common in asynchronous code where the traceback can be swallowed by a task.
The formatting configuration in the pyproject file is the other half of the style story. Black runs with a line length of 88, and isort uses the black profile with a line length of 100, multi-line output mode 3, trailing commas, and a target Python version of 310. Using isort's black profile is what keeps the two tools from fighting, and the two different line lengths are a small inconsistency that the tools tolerate because isort only decides where the imports go.
The lint pipeline runs four tools over the same code, which is a lot of checking for a library of this size, and it is a reasonable trade for a package that many people depend on for a function that either works or silently fails to send.
Editorial conclusion
Adopt fastapi-mail if you already run FastAPI and want email sending that fits the framework, with typed settings, a template engine and background task support rather than a pile of smtplib calls in a route handler. Do not adopt it if you need a full email platform, since this is a sending library rather than a campaign manager, and check the redis extra before you rely on the background task path. Before you start, read the connection configuration carefully, because the STARTTLS and SSL flags in it are mutually exclusive in practice, and set VALIDATE_CERTS correctly for any server that uses a private certificate authority.
Frequently asked questions
What does fastapi-mail do?
It is a simple, lightweight mail system for FastAPI that sends emails, attachments and HTML templates, individually and in bulk. Messages are Pydantic models, the connection settings are a typed configuration object, and the send call is awaited so it fits an async route.
How do I install and start sending with fastapi-mail?
Run pip install fastapi-mail, build a ConnectionConfig with the server, port, credentials and transport flags, create a MessageSchema with the subject, recipients, body and subtype, and await FastMail(conf).send_message(message) from an async route.
Which dependencies does fastapi-mail use and why?
aiosmtplib provides asynchronous SMTP so the send call can be awaited, jinja2 renders HTML templates, pydantic and pydantic-settings provide the typed message and settings, email-validator checks addresses, httpx and cryptography cover the HTTP sending path, starlette integrates it with the framework, and blinker supports the background task signalling. There is an optional redis extra for that background path.
What is the difference between MAIL_STARTTLS and MAIL_SSL_TLS in fastapi-mail?
They select the transport. The README example pairs port 465 with MAIL_SSL_TLS set to True and MAIL_STARTTLS set to False, which is implicit TLS, while a server expecting STARTTLS on port 587 wants the opposite. The library does not choose for you, because it depends on what the server advertises.
How is fastapi-mail tested and maintained?
The Makefile runs isort, black, flake8 and mypy for linting, and the test target runs pytest with branch coverage over the fastapi_mail package, producing both terminal and HTML reports. The dev dependencies include pytest-asyncio, pytest-mock, pytest-cov and fakeredis, and the recent releases are v1.6.6, v1.6.7 and v1.6.8 from 2026-08-22.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sabuhish-fastapi-mail)Was this useful?