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:
uv add "mailbridge[async]"
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
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:
| Provider | Async strategy | Notes |
|---|---|---|
| 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:
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:
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:
# 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
# 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?
MailBridge | AsyncMailBridge | |
|---|---|---|
| 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.