Docs / mailbridge / Async Support

Async Support

AsyncMailBridge is the async counterpart to MailBridge. It exposes the exact same API — send(), send_bulk(), context manager — but every method is a coroutine. Designed for FastAPI, Starlette, and any asyncio-based application.

Installation

Async support requires the [async] extra, which installs aiohttp and aiosmtplib:

bash
uv add "mailbridge[async]"
bash
pip install "mailbridge[async]"

AsyncMailBridge works without the [async] extra — it falls back to a thread pool executor automatically. Install the extra to get native non-blocking I/O.

Quick Example

python
import asyncio
from mailbridge import AsyncMailBridge

async def main():
    async with AsyncMailBridge(provider='sendgrid', api_key='SG.xxxxx') as mailer:
        response = await mailer.send(
            to='user@example.com',
            subject='Hello!',
            body='

Sent asynchronously.

' ) print(f"Sent! Message ID: {response.message_id}") asyncio.run(main())

The API is identical to MailBridge — the only differences are async with instead of with, and await before each call.

How It Works Per Provider

Each provider uses the most appropriate async strategy:

ProviderAsync strategyNotes
SendGrid Native — aiohttp Single ClientSession shared across all concurrent requests in a bulk send
Postmark Native — aiohttp Single ClientSession shared across all concurrent requests in a bulk send
Mailgun Native — aiohttp Single ClientSession shared across all concurrent requests in a bulk send
Brevo Native — aiohttp Uses Brevo's batch endpoint — single request for entire bulk
SMTP Native — aiosmtplib Bulk sends reuse a single async SMTP connection for the whole batch
Amazon SES Thread pool executor boto3 has no async SDK — calls run concurrently in a thread pool without blocking the event loop

If aiohttp or aiosmtplib are not installed, all providers fall back to running their sync implementation in a thread pool executor. The fallback is transparent — your code doesn't need to change.

Async Bulk Sending

Async bulk sends fire all requests concurrently via asyncio.gather — significantly faster than sequential sending for large batches:

python
import asyncio
from mailbridge import AsyncMailBridge, EmailMessageDto

async def send_newsletter(subscribers: list[dict]):
    messages = [
        EmailMessageDto(
            to=sub['email'],
            template_id='d-newsletter',
            template_data={'name': sub['name']}
        )
        for sub in subscribers
    ]

    async with AsyncMailBridge(provider='sendgrid', api_key='SG.xxxxx') as mailer:
        result = await mailer.send_bulk(messages)

    print(f"Sent: {result.successful}/{result.total}, Failed: {result.failed}")

asyncio.run(send_newsletter(subscribers))

You can also pass a pre-built BulkEmailDTO directly, same as with the sync client:

python
from mailbridge import AsyncMailBridge, EmailMessageDto, BulkEmailDTO

bulk = BulkEmailDTO(
    messages=messages,
    default_from='news@yourdomain.com',
    tags=['newsletter', 'march-2026']
)

async with AsyncMailBridge(provider='sendgrid', api_key='SG.xxxxx') as mailer:
    result = await mailer.send_bulk(bulk)

FastAPI Integration

Initialize AsyncMailBridge once during app lifespan, inject it as a dependency:

python
# mail.py
import os
from mailbridge import AsyncMailBridge

mailer = AsyncMailBridge(
    provider=os.getenv('MAIL_PROVIDER', 'smtp'),
    api_key=os.getenv('MAIL_API_KEY'),
    from_email=os.getenv('MAIL_FROM', 'noreply@yourdomain.com')
)

async def get_mailer() -> AsyncMailBridge:
    return mailer
python
# main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from mailbridge import AsyncMailBridge
from mail import mailer, get_mailer

@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    await mailer.close()   # Clean up on shutdown

app = FastAPI(lifespan=lifespan)

@app.post("/register")
async def register(
    email: str,
    name: str,
    mailer: AsyncMailBridge = Depends(get_mailer)
):
    # Non-blocking — event loop is free during the await
    await mailer.send(
        to=email,
        template_id='d-welcome',
        template_data={'name': name}
    )
    return {"status": "ok"}

Sync vs Async — Which to Use?

MailBridgeAsyncMailBridge
API style Synchronous async/await
Best for Scripts, Django, Flask, CLI tools FastAPI, Starlette, asyncio apps
Bulk concurrency Sequential Concurrent via asyncio.gather
SES / boto3 Direct call Thread pool (boto3 has no async SDK)
Context manager with MailBridge(...) async with AsyncMailBridge(...)
Requires [async] extra No For native I/O — falls back otherwise

Both clients share the same provider registry. Calling MailBridge.register_provider() makes the custom provider available to AsyncMailBridge as well — no need to register twice.