Vanty App: ESP-agnostic transactional mail, templates, webhooks.
Project description
Vanty Mail
ESP-agnostic transactional mail toolkit for FastAPI with Tortoise ORM. One
typed MailApp API, swappable backends (Resend, SMTP, Mailgun, SendGrid,
Postmark, Memory), Jinja2 templates with safe filters, normalized provider
webhooks, and tenant-scoped storage.
Inspired by django-anymail — re-implemented for
FastAPI + Tortoise ORM + httpx with vanty-core events.
Installation
pip install vanty-mail
# or
uv pip install vanty-mail
Quick start
from contextlib import asynccontextmanager
from fastapi import FastAPI
from vanty_mail import MailSettings, mount_mail_router
settings = MailSettings(
database_url="sqlite://./vanty-mail.db",
default_backend="resend",
resend_api_key="re_...",
default_from_email="hello@example.com",
)
app = FastAPI()
kit = mount_mail_router(app, settings=settings)
app.include_router(kit.admin_router, prefix="/admin/mail")
@asynccontextmanager
async def lifespan(_: FastAPI):
await kit.init_orm(generate_schemas=True)
try:
yield
finally:
await kit.close_orm()
app.router.lifespan_context = lifespan
Sending mail
# Direct send
from vanty_mail import EmailMessage
await kit.mail_service.send(
EmailMessage(
to=["alice@example.com"],
subject="Welcome",
html="<h1>Hi Alice</h1>",
text="Hi Alice",
),
)
# Template send (uses an EmailTemplate row)
await kit.mail_service.send_template(
"welcome",
to=["alice@example.com"],
context={"name": "Alice"},
organization_id=org_id,
)
ESP switching cookbook
Pick a backend at runtime via the backend= kwarg, or switch the default in
MailSettings.default_backend. Each backend reads its own creds from
settings (or from the encrypted ESPSetting rows in the DB).
| Backend | default_backend |
Required settings |
|---|---|---|
| Resend | resend |
resend_api_key, optional resend_webhook_secret |
| SMTP | smtp |
smtp_host, smtp_port, smtp_username, smtp_password |
| Memory | memory |
(test-only; records sent messages on the backend instance) |
| Mailgun | mailgun |
stub — raises NotImplementedError |
| SendGrid | sendgrid |
stub — raises NotImplementedError |
| Postmark | postmark |
stub — raises NotImplementedError |
await kit.mail_service.send(message, backend="smtp")
The Mailgun, SendGrid, and Postmark backends are intentionally stubbed — they
follow the same interface and raise NotImplementedError with a clear hint
(e.g. "set MAILGUN_API_KEY ..."). Drop in your own implementation by
subclassing EmailBackend and registering with register_backend("mailgun", ...).
Webhooks
Mount once and provider events become normalized vanty_core.events:
POST /mail/webhooks/resend
POST /mail/webhooks/mailgun
...
Each provider's payload is parsed into the same WebhookEvent shape and the
matching domain event is published (vanty_mail.mail.delivered, .opened,
.clicked, .bounced, .complained). Subscribe with
from vanty_core.events import on.
Encryption
ESP credentials and webhook secrets are encrypted at rest with Fernet from
the cryptography package. Set MAIL_ENCRYPTION_KEY (a urlsafe base64-encoded
32-byte key) in your environment. If unset, an ephemeral key is generated
in-process and a loud warning is logged — values written to the DB will be
unreadable on restart, so this mode is only useful for tests.
Generate a key with:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Events
All published via vanty_core.events.publish:
vanty_mail.mail.sentvanty_mail.mail.deliveredvanty_mail.mail.openedvanty_mail.mail.clickedvanty_mail.mail.bouncedvanty_mail.mail.complainedvanty_mail.mail.template_changedvanty_mail.mail.inbound_received
Publishing to GitHub
This package lives inside the Vanty monorepo but ships as a standalone repo. Use the bundled Makefile target:
make publish-github REPO=git@github.com:advantch/vanty-mail.git
Requires git-filter-repo
(pip install git-filter-repo). The target clones the monorepo to a temp
directory, filters history down to vanty-mail/, adds the new remote, and
pushes — your local monorepo checkout is never modified.
Known limitations
- The Mailgun, SendGrid, and Postmark backends are stubs. They raise
NotImplementedErrorwith a clear hint — implement and register your own to enable. - The default Tortoise SQLite driver is fine for tests; use Postgres in
production for
OrganizationScopedModelto behave correctly under load. - The
MAIL_ENCRYPTION_KEYephemeral fallback emits a warning every process start; do not rely on it outside of tests.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vanty_mail-0.3.0.tar.gz.
File metadata
- Download URL: vanty_mail-0.3.0.tar.gz
- Upload date:
- Size: 109.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e42aba6919a1454b03aac381f5bae2d4fa2ec2f9f67cabd4620ef0fc4cc2ddd
|
|
| MD5 |
adca1583d6de502f3bc4a30114e55c79
|
|
| BLAKE2b-256 |
d6591e289a5a05df91df9c86c541b8305b499404e21ad0b1bdd9897af55ab4b2
|
Provenance
The following attestation bundles were made for vanty_mail-0.3.0.tar.gz:
Publisher:
release.yml on advantch/vanty-mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vanty_mail-0.3.0.tar.gz -
Subject digest:
6e42aba6919a1454b03aac381f5bae2d4fa2ec2f9f67cabd4620ef0fc4cc2ddd - Sigstore transparency entry: 1409606717
- Sigstore integration time:
-
Permalink:
advantch/vanty-mail@a729935327677cd822a495bfa18020f7d6ba9eec -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a729935327677cd822a495bfa18020f7d6ba9eec -
Trigger Event:
push
-
Statement type:
File details
Details for the file vanty_mail-0.3.0-py3-none-any.whl.
File metadata
- Download URL: vanty_mail-0.3.0-py3-none-any.whl
- Upload date:
- Size: 31.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
940f09895304ed43f5b3601bd50f7c10f46ca23a1003fa34871eb882d455f598
|
|
| MD5 |
0c4afbbbaa966e80e24ead71ff03d419
|
|
| BLAKE2b-256 |
f53256058dec29757bc56c0956c6d830e985482684ed88c9b8a5dddf8e523b68
|
Provenance
The following attestation bundles were made for vanty_mail-0.3.0-py3-none-any.whl:
Publisher:
release.yml on advantch/vanty-mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vanty_mail-0.3.0-py3-none-any.whl -
Subject digest:
940f09895304ed43f5b3601bd50f7c10f46ca23a1003fa34871eb882d455f598 - Sigstore transparency entry: 1409606769
- Sigstore integration time:
-
Permalink:
advantch/vanty-mail@a729935327677cd822a495bfa18020f7d6ba9eec -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a729935327677cd822a495bfa18020f7d6ba9eec -
Trigger Event:
push
-
Statement type: