Vanty App: ESP-agnostic transactional mail, templates, webhooks.
Project description
Vanty Mail
Vanty Mail is the transactional email package for the Vanty ecosystem. It gives
FastAPI applications one typed mail API, tenant-scoped persistence with Tortoise
ORM, Jinja2 templates, provider webhooks, and vanty-core domain events.
Supported delivery backends:
- Resend
- SMTP
- Mailgun
- SendGrid
- Postmark
- Memory, for tests and local development
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()
mail = mount_mail_router(app, settings=settings)
app.include_router(mail.admin_router, prefix="/admin/mail")
@asynccontextmanager
async def lifespan(_: FastAPI):
await mail.init_orm(generate_schemas=True)
try:
yield
finally:
await mail.close_orm()
app.router.lifespan_context = lifespan
Send Email
from vanty_mail import EmailAttachment, EmailMessage
await mail.mail_service.send(
EmailMessage(
to=["alice@example.com"],
subject="Welcome to Vanty",
html="<h1>Hi Alice</h1>",
text="Hi Alice",
tags=["welcome"],
metadata={"user_id": "usr_123"},
attachments=[
EmailAttachment(
filename="receipt.txt",
content=b"Thanks for your order",
content_type="text/plain",
)
],
),
)
Send with a specific provider for one call:
await mail.mail_service.send(message, backend="postmark")
Templates
send_template renders an active EmailTemplate row for the current
organization and sends the resulting message through any configured backend.
await mail.mail_service.send_template(
"welcome",
to=["alice@example.com"],
context={"name": "Alice"},
organization_id=org_id,
)
Templates use Jinja2 with Vanty Mail's safe rendering filters. The rendered
subject, HTML body, and text body are persisted through the normal
SentMessage flow.
Provider Configuration
Set MailSettings.default_backend to the provider you want by default. Each
provider can also be selected per message with backend=.
| Backend | Setting value | Required settings |
|---|---|---|
| Resend | resend |
resend_api_key |
| SMTP | smtp |
smtp_host, smtp_port, and credentials when your SMTP server requires auth |
| Mailgun | mailgun |
mailgun_api_key, mailgun_domain |
| SendGrid | sendgrid |
sendgrid_api_key |
| Postmark | postmark |
postmark_server_token |
| Memory | memory |
No external credentials |
Optional provider settings:
| Setting | Purpose |
|---|---|
resend_api_url |
Override the Resend API base URL |
resend_webhook_secret |
Verify Resend Svix webhooks |
mailgun_api_url |
Use Mailgun's US or EU API base URL |
mailgun_webhook_signing_key |
Verify Mailgun webhook signatures |
sendgrid_api_url |
Use global or EU SendGrid API base URL |
sendgrid_webhook_public_key |
Verify SendGrid signed Event Webhooks |
postmark_api_url |
Override the Postmark API base URL |
postmark_message_stream |
Choose the Postmark message stream |
postmark_webhook_secret |
Verify a shared secret sent as a custom Postmark webhook header |
Environment variables use the MAIL_ prefix. For example:
MAIL_DEFAULT_BACKEND=mailgun
MAIL_MAILGUN_API_KEY=key-...
MAIL_MAILGUN_DOMAIN=mg.example.com
MAIL_DEFAULT_FROM_EMAIL=hello@example.com
Webhooks
Mount the mail router once and point each provider at its endpoint:
POST /mail/webhooks/resend
POST /mail/webhooks/mailgun
POST /mail/webhooks/sendgrid
POST /mail/webhooks/postmark
Vanty Mail verifies provider signatures when the matching secret or public key
is configured, parses provider payloads, updates matching SentMessage rows,
and publishes normalized events:
vanty_mail.mail.deliveredvanty_mail.mail.openedvanty_mail.mail.clickedvanty_mail.mail.bouncedvanty_mail.mail.complained
Subscribe with vanty_core.events.on:
from vanty_core.events import on
from vanty_mail.events import MailBounced
@on(MailBounced)
async def handle_bounce(event: MailBounced) -> None:
...
For Postmark webhook verification, configure a custom webhook HTTP header named
X-Vanty-Mail-Webhook-Secret with the value stored in
MAIL_POSTMARK_WEBHOOK_SECRET.
Encryption
ESP credentials and webhook secrets stored in ESPSetting rows are encrypted at
rest with Fernet. Set MAIL_ENCRYPTION_KEY to a urlsafe base64-encoded 32-byte
key before using database-backed provider settings.
Generate a key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
If the key is unset, Vanty Mail creates an in-process key and logs a warning. That mode is useful for tests only because encrypted values cannot be read after the process restarts.
Data Model
EmailTemplatestores tenant-scoped reusable subjects and bodies.ESPSettingstores encrypted provider credentials and defaults.SentMessagetracks outbound message status, provider IDs, timestamps, and bounce data.InboundMessagestores inbound email payloads for providers that forward inbound mail.
Use Postgres in production. SQLite is convenient for tests and small local experiments, but production tenant-scoped workloads should use a database with proper concurrency behavior.
Development
uv sync
uv run pytest
uv run ruff check .
uv build
Publishing to GitHub
This package lives inside the Vanty monorepo and can be published as a standalone repository:
make publish-github REPO=git@github.com:advantch/vanty-mail.git
The target clones the monorepo to a temporary directory, filters history down to
vanty-mail/, adds the target remote, and pushes. Your local monorepo checkout
is not modified by the publish target.
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.4.0.tar.gz.
File metadata
- Download URL: vanty_mail-0.4.0.tar.gz
- Upload date:
- Size: 114.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 |
d66c69935b8117087204d8cf05bdf74d30f0f7e91fd362857dd08aaeda325767
|
|
| MD5 |
45950641ad9a1eb0c62c65996c2d4ffa
|
|
| BLAKE2b-256 |
fecaf7cab9185090d5c22344b568b2338c0edd2b7a35254541089dfd9bf5961b
|
Provenance
The following attestation bundles were made for vanty_mail-0.4.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.4.0.tar.gz -
Subject digest:
d66c69935b8117087204d8cf05bdf74d30f0f7e91fd362857dd08aaeda325767 - Sigstore transparency entry: 1432893048
- Sigstore integration time:
-
Permalink:
advantch/vanty-mail@8f51d697dad2ccd701cce2eea6f9d4fc63cb41df -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f51d697dad2ccd701cce2eea6f9d4fc63cb41df -
Trigger Event:
push
-
Statement type:
File details
Details for the file vanty_mail-0.4.0-py3-none-any.whl.
File metadata
- Download URL: vanty_mail-0.4.0-py3-none-any.whl
- Upload date:
- Size: 37.2 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 |
6d67699b5044a170387c47812e705e41b25505e88e310dd91429bbe986f17154
|
|
| MD5 |
f38059107d3df8e1ab8ce88dfe0c59c0
|
|
| BLAKE2b-256 |
41dbb9160dfed54964dd678acf0b2d6a17bfc6ae5d14a43816f3a3be1337a52e
|
Provenance
The following attestation bundles were made for vanty_mail-0.4.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.4.0-py3-none-any.whl -
Subject digest:
6d67699b5044a170387c47812e705e41b25505e88e310dd91429bbe986f17154 - Sigstore transparency entry: 1432893147
- Sigstore integration time:
-
Permalink:
advantch/vanty-mail@8f51d697dad2ccd701cce2eea6f9d4fc63cb41df -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/advantch
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f51d697dad2ccd701cce2eea6f9d4fc63cb41df -
Trigger Event:
push
-
Statement type: