Install
$ agentstack add skill-litestar-org-litestar-skills-litestar-email ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
litestar-email
litestar-email provides a pluggable email-sending abstraction for Litestar. One config + plugin, swap backends without touching call sites.
Backends:
SMTPConfig— generic SMTP viaaiosmtplibResendConfig— Resend HTTP APISendGridConfig— SendGrid HTTP APIMailgunConfig— Mailgun HTTP APISESConfig— Amazon SES API v2backend="memory"/InMemoryBackend— for tests; captures messages inInMemoryBackend.outboxbackend="console"— for local development; prints messages
Code Style Rules
- PEP 604 unions:
T | None, neverOptional[T] - Consumer Litestar app modules MAY use
from __future__ import annotations - Async all I/O —
EmailService.send_messageisasync
Quick Reference
Install
pip install litestar-email
# Optional extras for specific backends:
pip install litestar-email[smtp]
pip install litestar-email[ses]
pip install litestar-email[aiohttp]
Basic Setup
from litestar import Litestar
from litestar_email import EmailPlugin, EmailConfig, SMTPConfig
app = Litestar(plugins=[EmailPlugin(config=EmailConfig(
backend=SMTPConfig(
host="smtp.example.com",
port=587,
use_tls=True,
username="user@example.com",
password="secret",
),
from_email="noreply@example.com",
from_name="My App",
))])
EmailConfig
| Option | Type | Description | | --- | --- | --- | | backend | str \| BackendConfig | One of "console", "memory", SMTPConfig, ResendConfig, SendGridConfig, MailgunConfig, SESConfig | | from_email | str | Default sender address | | from_name | str \| None | Optional display name |
Backend Configs
SMTPConfig
from litestar_email import SMTPConfig
SMTPConfig(
host="smtp.gmail.com",
port=587,
use_tls=True, # STARTTLS
use_ssl=False, # Implicit SSL (port 465)
username="you@gmail.com",
password="app-password",
timeout=10,
)
ResendConfig
from litestar_email import ResendConfig
ResendConfig(api_key="re_xxxxxxxxxx")
SendGridConfig
from litestar_email import SendGridConfig
SendGridConfig(api_key="SG.xxxxxxxxxx")
MailgunConfig
from litestar_email import MailgunConfig
MailgunConfig(api_key="key-xxxxxxxxxx", domain="mg.example.com", region="us")
Memory backend (testing)
from litestar_email import EmailConfig
from litestar_email.backends import InMemoryBackend
InMemoryBackend.clear()
config = EmailConfig(backend="memory", from_email="test@example.com")
# Stores sent messages in memory; inspect InMemoryBackend.outbox
Dependency Injection
EmailPlugin.on_app_init registers an EmailService dependency as mailer by default. Override email_service_dependency_key if the project already standardizes on another parameter name.
from litestar import post
from litestar_email import EmailService, EmailMessage
@post("/send-notification")
async def send_notification(
mailer: EmailService,
data: NotificationRequest,
) -> dict:
await mailer.send_message(EmailMessage(
to=[data.recipient],
subject="Notification",
body="You have a new notification.",
html_body="You have a new notification.",
))
return {"sent": True}
EmailMessage
from litestar_email import EmailMessage
EmailMessage(
to=["recipient@example.com"], # required
subject="Hello", # required
body="Plain text body", # optional
html_body="HTML body", # optional
cc=["cc@example.com"],
bcc=["bcc@example.com"],
reply_to="reply@example.com",
from_email="override@example.com", # overrides EmailConfig default
from_name="Override Name",
headers={"X-Custom": "value"},
attachments=[("/path/to/file.pdf", "application/pdf")],
)
EmailMultiAlternatives
from litestar_email import EmailMultiAlternatives
msg = EmailMultiAlternatives(
to=["user@example.com"],
subject="Welcome",
body="Welcome to our platform.",
html_body="Welcome to our platform.",
)
await email_service.send_message(msg)
EmailService Methods
| Method | Description | | --- | --- | | send_message(msg) | Send a single EmailMessage | | send_messages(msgs) | Batch send |
Both are async.
Connection Pooling (SMTP)
async with email_service as svc:
await svc.send_message(msg1)
await svc.send_message(msg2)
Standalone Usage (no DI)
from litestar_email import EmailConfig, SMTPConfig, EmailMessage
config = EmailConfig(
backend=SMTPConfig(host="smtp.example.com", port=587, use_tls=True),
from_email="noreply@example.com",
)
async def main():
async with config.provide_service() as email_service:
await email_service.send_message(EmailMessage(
to=["user@example.com"], subject="Hello", body="World",
))
Templating
litestar-email does not ship a templating engine. Use Litestar's Jinja2 integration to render body / html_body strings before constructing EmailMessage:
from litestar.template import TemplateEngineProtocol
async def send_welcome(
mailer: EmailService,
template_engine: TemplateEngineProtocol,
user: User,
) -> None:
html = template_engine.render("emails/welcome.html", {"user": user})
text = template_engine.render("emails/welcome.txt", {"user": user})
await mailer.send_message(EmailMessage(
to=[user.email],
subject="Welcome!",
body=text,
html_body=html,
))
Workflow
Step 1: Install + Pick Backend
| Need | Backend | | --- | --- | | Generic SMTP / corporate mail | SMTPConfig | | Modern transactional API | ResendConfig (preferred for new projects) | | Existing SendGrid contract | SendGridConfig | | Mailgun account | MailgunConfig | | Any test environment | backend="memory" / InMemoryBackend | | AWS-native transactional mail | SESConfig |
Step 2: Configure Plugin
Build EmailConfig(backend=..., from_email=..., from_name=...) and wrap in EmailPlugin. Add to Litestar(plugins=[...]).
Step 3: Inject EmailService
In handlers / services, declare email_service: EmailService parameter. Litestar's DI provides it.
Step 4: Construct EmailMessage
Use EmailMessage for simple sends. Use EmailMultiAlternatives if you need multiple HTML parts. Render templates separately if needed.
Step 5: Background Send (recommended for slow ops)
For non-interactive flows, enqueue email sending via litestar-saq rather than blocking the request. See ../litestar-saq/SKILL.md.
await task_queues.get("default").enqueue(
"send_welcome_email",
user_id=user.id,
timeout=30,
retries=2,
key=f"welcome-{user.id}",
)
Step 6: Test with InMemoryBackend
In test config, swap backend="memory". Clear and assert against InMemoryBackend.outbox.
Guardrails
- Use
backend="memory"in all test environments — no real network calls;InMemoryBackend.outboxcaptures messages for assertions. - Background-queue email sends — use
litestar-saqfor transactional email. SMTP can be slow; blocking handlers degrades p99. - Set
from_emailat the plugin level — overriding per message is for exceptions, not the default. - Use
ResendorSendGridfor high-volume transactional — direct SMTP scales poorly past ~100/s. - Never log passwords/API keys — sanitize
EmailConfig.backendbefore structlog dumps. - Validate recipient addresses at the API boundary — invalid addresses cause backend errors and waste retries.
- Set timeouts —
SMTPConfig.timeoutdefaults are usually fine; tune if your SMTP host is slow. - Don't ship unused extras —
[smtp],[ses], and[aiohttp]are opt-in dependencies.
Validation Checkpoint
Before delivering email-sending code, verify:
- [ ]
EmailPluginis inapp.plugins - [ ] Backend is appropriate for env (
backend="memory"in tests, real backend in dev/prod) - [ ]
from_emailis configured at theEmailConfiglevel - [ ] Handler injects
EmailServicevia DI, usually asmailer - [ ]
EmailMessageis constructed with requiredtoandsubject - [ ] Slow / retry-able sends are queued via
litestar-saqinstead of blocking the request - [ ] Tests assert against
InMemoryBackend.outbox - [ ] Secrets (
password,api_key) come from env / settings, not hard-coded
Example
Task: Welcome-email flow that queues a SAQ task to send via Resend; test asserts via InMemoryBackend.
# app/config/email.py
from litestar_email import EmailConfig, ResendConfig
from app.lib.settings import get_settings
def get_email_config() -> EmailConfig:
settings = get_settings()
if settings.env == "test":
return EmailConfig(backend="memory", from_email="test@example.com")
return EmailConfig(
backend=ResendConfig(api_key=settings.resend.api_key),
from_email=settings.email.from_email,
from_name=settings.email.from_name,
)
# app/server/plugins.py
from litestar_email import EmailPlugin
from app.config.email import get_email_config
email = EmailPlugin(config=get_email_config())
# app/domain/accounts/tasks.py
from litestar_email import EmailMessage
async def send_welcome_email(ctx: dict, *, user_id: int, email: str, name: str) -> None:
"""Send welcome email as a SAQ background task."""
email_service = ctx["state"]["email_service"]
template_engine = ctx["state"]["template_engine"]
html = template_engine.render("emails/welcome.html", {"name": name})
await email_service.send_message(EmailMessage(
to=[email],
subject=f"Welcome, {name}!",
body=f"Welcome, {name}!",
html_body=html,
))
# app/domain/accounts/controllers.py
from litestar import Controller, post
from litestar_saq import TaskQueues
class AccountController(Controller):
path = "/api/accounts"
@post("/")
async def create_account(self, data: AccountCreate, task_queues: TaskQueues) -> Account:
user = await self.create(data)
await task_queues.get("default").enqueue(
"send_welcome_email",
user_id=user.id, email=user.email, name=user.name,
timeout=30, retries=2, key=f"welcome-{user.id}",
)
return user
# tests/test_accounts.py
async def test_account_creation_queues_welcome_email(client, email_service):
from litestar_email.backends import InMemoryBackend
InMemoryBackend.clear()
resp = await client.post("/api/accounts", json={"email": "alice@example.com", "name": "Alice"})
assert resp.status_code == 201
# After SAQ flush in test:
assert len(InMemoryBackend.outbox) == 1
assert InMemoryBackend.outbox[0].subject == "Welcome, Alice!"
Cross-References
- [litestar](../litestar/SKILL.md) — DI, plugin lifecycle.
- [litestar-saq](../litestar-saq/SKILL.md) — Background-queue email sends.
- [litestar-testing](../litestar-testing/SKILL.md) — Testing flows that send email.
Official References
Shared Styleguide Baseline
- [General Principles](../litestar-styleguide/references/general.md)
- [Python](../litestar-styleguide/references/python.md)
- [Litestar](../litestar-styleguide/references/litestar.md)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: litestar-org
- Source: litestar-org/litestar-skills
- License: MIT
- Homepage: https://github.com/litestar-org/litestar-skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.