From litestar
Configures litestar-email plugin for Litestar apps with multiple backends: SMTP, Resend, SendGrid, Mailgun, SES, memory/console for testing. Auto-activates when email classes appear.
How this skill is triggered — by the user, by Claude, or both
Slash command
/litestar:litestar-emailThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`litestar-email` provides a pluggable email-sending abstraction for Litestar. One config + plugin, swap backends without touching call sites.
litestar-email provides a pluggable email-sending abstraction for Litestar. One config + plugin, swap backends without touching call sites.
Backends:
SMTPConfig — generic SMTP via aiosmtplibResendConfig — Resend HTTP APISendGridConfig — SendGrid HTTP APIMailgunConfig — Mailgun HTTP APISESConfig — Amazon SES API v2backend="memory" / InMemoryBackend — for tests; captures messages in InMemoryBackend.outboxbackend="console" — for local development; prints messagesT | None, never Optional[T]from __future__ import annotationsEmailService.send_message is asyncpip install litestar-email
# Optional extras for specific backends:
pip install litestar-email[smtp]
pip install litestar-email[ses]
pip install litestar-email[aiohttp]
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="[email protected]",
password="secret",
),
from_email="[email protected]",
from_name="My App",
))])
| 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 |
from litestar_email import SMTPConfig
SMTPConfig(
host="smtp.gmail.com",
port=587,
use_tls=True, # STARTTLS
use_ssl=False, # Implicit SSL (port 465)
username="[email protected]",
password="app-password",
timeout=10,
)
from litestar_email import ResendConfig
ResendConfig(api_key="re_xxxxxxxxxx")
from litestar_email import SendGridConfig
SendGridConfig(api_key="SG.xxxxxxxxxx")
from litestar_email import MailgunConfig
MailgunConfig(api_key="key-xxxxxxxxxx", domain="mg.example.com", region="us")
from litestar_email import EmailConfig
from litestar_email.backends import InMemoryBackend
InMemoryBackend.clear()
config = EmailConfig(backend="memory", from_email="[email protected]")
# Stores sent messages in memory; inspect InMemoryBackend.outbox
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="<p>You have a new notification.</p>",
))
return {"sent": True}
from litestar_email import EmailMessage
EmailMessage(
to=["[email protected]"], # required
subject="Hello", # required
body="Plain text body", # optional
html_body="<p>HTML body</p>", # optional
cc=["[email protected]"],
bcc=["[email protected]"],
reply_to="[email protected]",
from_email="[email protected]", # overrides EmailConfig default
from_name="Override Name",
headers={"X-Custom": "value"},
attachments=[("/path/to/file.pdf", "application/pdf")],
)
from litestar_email import EmailMultiAlternatives
msg = EmailMultiAlternatives(
to=["[email protected]"],
subject="Welcome",
body="Welcome to our platform.",
html_body="<p>Welcome to our platform.</p>",
)
await email_service.send_message(msg)
| Method | Description |
|---|---|
send_message(msg) | Send a single EmailMessage |
send_messages(msgs) | Batch send |
Both are async.
async with email_service as svc:
await svc.send_message(msg1)
await svc.send_message(msg2)
from litestar_email import EmailConfig, SMTPConfig, EmailMessage
config = EmailConfig(
backend=SMTPConfig(host="smtp.example.com", port=587, use_tls=True),
from_email="[email protected]",
)
async def main():
async with config.provide_service() as email_service:
await email_service.send_message(EmailMessage(
to=["[email protected]"], subject="Hello", body="World",
))
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,
))
| 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 |
Build EmailConfig(backend=..., from_email=..., from_name=...) and wrap in EmailPlugin. Add to Litestar(plugins=[...]).
In handlers / services, declare email_service: EmailService parameter. Litestar's DI provides it.
Use EmailMessage for simple sends. Use EmailMultiAlternatives if you need multiple HTML parts. Render templates separately if needed.
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}",
)
In test config, swap backend="memory". Clear and assert against InMemoryBackend.outbox.
backend="memory" in all test environments — no real network calls; InMemoryBackend.outbox captures messages for assertions.litestar-saq for transactional email. SMTP can be slow; blocking handlers degrades p99.from_email at the plugin level — overriding per message is for exceptions, not the default.Resend or SendGrid for high-volume transactional — direct SMTP scales poorly past ~100/s.EmailConfig.backend before structlog dumps.SMTPConfig.timeout defaults are usually fine; tune if your SMTP host is slow.[smtp], [ses], and [aiohttp] are opt-in dependencies.Before delivering email-sending code, verify:
EmailPlugin is in app.pluginsbackend="memory" in tests, real backend in dev/prod)from_email is configured at the EmailConfig levelEmailService via DI, usually as mailerEmailMessage is constructed with required to and subjectlitestar-saq instead of blocking the requestInMemoryBackend.outboxpassword, api_key) come from env / settings, not hard-codedTask: 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="[email protected]")
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": "[email protected]", "name": "Alice"})
assert resp.status_code == 201
# After SAQ flush in test:
assert len(InMemoryBackend.outbox) == 1
assert InMemoryBackend.outbox[0].subject == "Welcome, Alice!"
npx claudepluginhub litestar-org/litestar-skills --plugin litestarSearches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.