Email support for Litestar applications. This plugin provides a pluggable email backend system for sending transactional emails with support for multiple providers.
pip install litestar-emailFor production backends, install with the appropriate extra:
# SMTP backend
pip install litestar-email[smtp]
# aiohttp transport (alternative to httpx for API backends)
pip install litestar-email[aiohttp]
# All optional dependencies
pip install litestar-email[all]Note: API backends (Resend, SendGrid, Mailgun) use
httpxwhich is bundled with Litestar. No extra installation is needed for these backends.
from litestar import Litestar
from litestar_email import EmailConfig, EmailPlugin
# Development: console output
config = EmailConfig(
backend="console",
from_email="noreply@example.com",
from_name="My App",
)
app = Litestar(plugins=[EmailPlugin(config=config)])For production, pass a backend config object directly:
from litestar_email import EmailConfig, EmailPlugin, SMTPConfig
config = EmailConfig(
backend=SMTPConfig(host="smtp.example.com", port=587, use_tls=True),
from_email="noreply@example.com",
)
app = Litestar(plugins=[EmailPlugin(config=config)])from litestar_email import EmailMessage
message = EmailMessage(
subject="Welcome!",
body="Thanks for signing up.",
to=["user@example.com"],
)
async with config.provide_service() as mailer:
await mailer.send_message(message)If message.from_email is omitted, the service uses config.from_email and
config.from_name as defaults.
The plugin registers a mailer dependency for handlers by default:
from litestar import get
from litestar_email import EmailMessage, EmailService
@get("/welcome/{email:str}")
async def send_welcome(email: str, mailer: EmailService) -> dict[str, str]:
message = EmailMessage(
subject="Welcome!",
body="Thanks for signing up.",
to=[email],
)
await mailer.send_message(message)
return {"status": "sent"}This works for both router handlers and controller methods the same way, since the dependency is registered on the app.
If you need a service outside of Litestar (e.g., for a worker), use
config.get_service() for a one-off instance or config.provide_service()
for batch sending.
Event listeners in Litestar execute outside request context and cannot receive DI-injected dependencies. Pass the mailer explicitly:
from litestar import Litestar, get
from litestar.events import listener
from litestar_email import EmailConfig, EmailMessage, EmailPlugin, EmailService, SMTPConfig
config = EmailConfig(
backend=SMTPConfig(host="localhost", port=1025),
from_email="noreply@example.com",
from_name="My App",
)
@get("/register/{email:str}")
async def register(email: str, mailer: EmailService) -> dict[str, str]:
# Pass the DI-injected mailer to the event
request.app.emit("user.registered", email, mailer=mailer)
return {"status": "queued"}
@listener("user.registered")
async def on_user_registered(email: str, mailer: EmailService) -> None:
# mailer is passed explicitly from emit(), not injected via DI
await mailer.send_message(
EmailMessage(subject="Welcome!", body="Thanks for signing up.", to=[email]),
)
app = Litestar(
plugins=[EmailPlugin(config=config)],
listeners=[on_user_registered],
)You can override the dependency and state keys via EmailConfig if needed:
email_service_dependency_key="email_service" and email_service_state_key="email_service".
Use the config helpers directly without Litestar:
from litestar_email import EmailConfig, EmailMessage, SMTPConfig
config = EmailConfig(
backend=SMTPConfig(host="localhost", port=1025),
from_email="noreply@example.com",
)
message = EmailMessage(
subject="Hello!",
body="This is a standalone send.",
to=["user@example.com"],
)
async with config.provide_service() as mailer:
await mailer.send_message(message)For batch sends, use the context manager to reuse the connection:
async with config.provide_service() as mailer:
await mailer.send_messages([message1, message2, message3])from litestar_email import EmailMultiAlternatives
message = EmailMultiAlternatives(
subject="Welcome!",
body="Thanks for signing up.", # Plain text fallback
html_body="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
to=["user@example.com"],
)| Backend | Config Class | Use Case |
|---|---|---|
console |
- | Development (prints to stdout) |
memory |
- | Testing (stores in memory) |
smtp |
SMTPConfig |
Production SMTP servers |
resend |
ResendConfig |
Resend HTTP API |
sendgrid |
SendGridConfig |
SendGrid HTTP API |
mailgun |
MailgunConfig |
Mailgun HTTP API |
API backends (Resend, SendGrid, Mailgun) support configurable HTTP transports:
from litestar_email import ResendConfig
# Default: uses httpx (bundled with Litestar)
config = ResendConfig(api_key="re_xxx...")
# Alternative: use aiohttp
config = ResendConfig(api_key="re_xxx...", http_transport="aiohttp")The InMemoryBackend is designed for testing:
from litestar_email.backends import InMemoryBackend
def test_sends_welcome_email():
InMemoryBackend.clear()
# ... code that sends email ...
assert len(InMemoryBackend.outbox) == 1
assert InMemoryBackend.outbox[0].subject == "Welcome!"If you need direct backend access in tests, use config.get_backend():
from litestar_email import EmailConfig, EmailMessage
config = EmailConfig(backend="memory")
backend = config.get_backend()
message = EmailMessage(subject="Hello", body="Body", to=["user@example.com"])
await backend.send_messages([message])make install # Install dependencies
make test # Run tests
make lint # Run linting
make check-all # Run all checksMIT