AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Litestar Email

skill-litestar-org-litestar-skills-litestar-email · by litestar-org

Auto-activate for litestar_email, EmailPlugin, EmailConfig, EmailService, EmailMessage, SMTPConfig, ResendConfig, SendGridConfig, MailgunConfig, SESConfig, or InMemoryBackend. Not for marketing platforms.

No reviews yet
0 installs
27 views
0.0% view→install

Install

$ agentstack add skill-litestar-org-litestar-skills-litestar-email

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-litestar-org-litestar-skills-litestar-email)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Litestar Email? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 via aiosmtplib
  • ResendConfig — Resend HTTP API
  • SendGridConfig — SendGrid HTTP API
  • MailgunConfig — Mailgun HTTP API
  • SESConfig — Amazon SES API v2
  • backend="memory" / InMemoryBackend — for tests; captures messages in InMemoryBackend.outbox
  • backend="console" — for local development; prints messages

Code Style Rules

  • PEP 604 unions: T | None, never Optional[T]
  • Consumer Litestar app modules MAY use from __future__ import annotations
  • Async all I/O — EmailService.send_message is async

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.outbox captures messages for assertions.
  • Background-queue email sends — use litestar-saq for transactional email. SMTP can be slow; blocking handlers degrades p99.
  • Set from_email at the plugin level — overriding per message is for exceptions, not the default.
  • Use Resend or SendGrid for high-volume transactional — direct SMTP scales poorly past ~100/s.
  • Never log passwords/API keys — sanitize EmailConfig.backend before structlog dumps.
  • Validate recipient addresses at the API boundary — invalid addresses cause backend errors and waste retries.
  • Set timeoutsSMTPConfig.timeout defaults 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:

  • [ ] EmailPlugin is in app.plugins
  • [ ] Backend is appropriate for env (backend="memory" in tests, real backend in dev/prod)
  • [ ] from_email is configured at the EmailConfig level
  • [ ] Handler injects EmailService via DI, usually as mailer
  • [ ] EmailMessage is constructed with required to and subject
  • [ ] Slow / retry-able sends are queued via litestar-saq instead 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.