From litestar-skills
Auto-activate for litestar_email imports, EmailPlugin, EmailConfig, EmailService, EmailMessage, SMTPConfig, ResendConfig, SendGridConfig, MailgunConfig, InMemoryConfig. The first-party Litestar plugin for sending email with pluggable backends and DI of EmailService. Produces EmailPlugin configs, backend selection (SMTP/Resend/SendGrid/Mailgun/InMemory), EmailMessage construction, multipart (text+HTML) handling, and DI patterns. Use when: sending transactional or notification email from a Litestar app, choosing an email backend, testing email flows with InMemoryConfig, or wiring the EmailService into handlers. Not for non-Litestar email (use the underlying SDK directly) or for marketing/bulk email campaigns at scale (use a dedicated marketing platform).
npx claudepluginhub litestar-org/litestar-skills --plugin litestar-skillsThis skill uses the workspace's default tool permissions.
`litestar-email` provides a pluggable email-sending abstraction for Litestar. One config + plugin, swap backends without touching call sites.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Builds scalable data pipelines, modern data warehouses, and real-time streaming architectures using Spark, dbt, Airflow, Kafka, and cloud platforms like Snowflake, BigQuery.
Builds production Apache Airflow DAGs with best practices for operators, sensors, testing, and deployment. For data pipelines, workflow orchestration, and batch job scheduling.
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 APIInMemoryConfig — for tests; captures messages in an outbox listT | None, never Optional[T]from __future__ import annotationsEmailService.send_message is asyncpip install litestar-email
# Optional extras for specific backends:
pip install litestar-email[resend]
pip install litestar-email[sendgrid]
pip install litestar-email[mailgun]
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",
))])
| Option | Type | Description |
|---|---|---|
backend | BackendConfig | One of SMTPConfig, ResendConfig, SendGridConfig, MailgunConfig, InMemoryConfig |
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="you@gmail.com",
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 InMemoryConfig
InMemoryConfig()
# Stores sent messages in memory; inspect via email_service.outbox
EmailPlugin.on_app_init registers EmailService automatically.
from litestar import post
from litestar_email import EmailService, EmailMessage
@post("/send-notification")
async def send_notification(
email_service: EmailService,
data: NotificationRequest,
) -> dict:
await email_service.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=["recipient@example.com"], # required
subject="Hello", # required
body="Plain text body", # optional
html_body="<p>HTML body</p>", # 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")],
)
from litestar_email import EmailMultiAlternatives
msg = EmailMultiAlternatives(
to=["user@example.com"],
subject="Welcome",
body="Welcome to our platform.",
)
msg.attach_alternative("<p>Welcome to our platform.</p>", "text/html")
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="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",
))
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(
email_service: 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 email_service.send_message(EmailMessage(
to=[user.email],
subject="Welcome!",
body=text,
html_body=html,
))
<workflow>
| 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 | InMemoryConfig |
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=InMemoryConfig(). Assert against email_service.outbox.
InMemoryConfig in all test environments — no real network calls; provides an outbox 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.[resend] / [sendgrid] extras you don't use — they pull in HTTP client deps.Before delivering email-sending code, verify:
EmailPlugin is in app.pluginsInMemoryConfig in tests, real backend in dev/prod)from_email is configured at the EmailConfig levelEmailService via DI (no module-level instance)EmailMessage is constructed with required to and subjectlitestar-saq instead of blocking the requestemail_service.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 InMemoryConfig.
# app/config/email.py
from litestar_email import EmailConfig, ResendConfig, InMemoryConfig
from app.lib.settings import get_settings
def get_email_config() -> EmailConfig:
settings = get_settings()
if settings.env == "test":
return EmailConfig(backend=InMemoryConfig(), 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):
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(email_service.outbox) == 1
assert email_service.outbox[0].subject == "Welcome, Alice!"
</example>