Skip to main content

Vanty App: ESP-agnostic transactional mail, templates, webhooks.

Project description

Vanty Mail

PyPI Python

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.delivered
  • vanty_mail.mail.opened
  • vanty_mail.mail.clicked
  • vanty_mail.mail.bounced
  • vanty_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

  • EmailTemplate stores tenant-scoped reusable subjects and bodies.
  • ESPSetting stores encrypted provider credentials and defaults.
  • SentMessage tracks outbound message status, provider IDs, timestamps, and bounce data.
  • InboundMessage stores 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vanty_mail-0.4.0.tar.gz (114.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vanty_mail-0.4.0-py3-none-any.whl (37.2 kB view details)

Uploaded Python 3

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

Hashes for vanty_mail-0.4.0.tar.gz
Algorithm Hash digest
SHA256 d66c69935b8117087204d8cf05bdf74d30f0f7e91fd362857dd08aaeda325767
MD5 45950641ad9a1eb0c62c65996c2d4ffa
BLAKE2b-256 fecaf7cab9185090d5c22344b568b2338c0edd2b7a35254541089dfd9bf5961b

See more details on using hashes here.

Provenance

The following attestation bundles were made for vanty_mail-0.4.0.tar.gz:

Publisher: release.yml on advantch/vanty-mail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for vanty_mail-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6d67699b5044a170387c47812e705e41b25505e88e310dd91429bbe986f17154
MD5 f38059107d3df8e1ab8ce88dfe0c59c0
BLAKE2b-256 41dbb9160dfed54964dd678acf0b2d6a17bfc6ae5d14a43816f3a3be1337a52e

See more details on using hashes here.

Provenance

The following attestation bundles were made for vanty_mail-0.4.0-py3-none-any.whl:

Publisher: release.yml on advantch/vanty-mail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page