Sending email from a SCALR app¶
Every app backend can send transactional email through a shared SMTP provider configured platform-wide. Authentik uses the same SMTP for its recovery (forgot-password) flow, so configuring once lights it up everywhere.
TL;DR¶
from scalr_email import send
send(
to="user@example.com",
subject="Your invoice is ready",
body="Plain-text body here.",
body_html="<p>HTML body here.</p>", # optional
)
Raises NotConfiguredError if SMTP isn't set up. Raises EmailError
if the SMTP server rejects the message.
How it's wired¶
scalr-email is a tiny stdlib-only wrapper around smtplib. It reads
the same SMTP_* env vars Authentik consumes, so a single block in
.env.shared drives both:
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USERNAME=resend
SMTP_PASSWORD=re_<api-key>
SMTP_FROM=Acme <hello@yourdomain.com>
SMTP_USE_TLS=true
In dev, leaving these blank is fine — apps will boot, but any
send() call raises NotConfiguredError. In prod, set them before
deploying.
For the production setup walkthrough (provider choice, DNS records,
verification), see DEPLOYMENT.md at the repo root → "Set up
transactional email."
Adding email to an existing app¶
If your app's backend was scaffolded after May 2026, scalr-email is
already installed and the SMTP env vars are already in your compose
file — just from scalr_email import send and go.
For older apps, add the package to your backend Dockerfile:
COPY packages/email-py /packages/email-py
RUN pip install --no-cache-dir -e /packages/email-py
…and forward the SMTP env vars in your docker-compose.yml's backend
service environment: block:
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
SMTP_USE_SSL: ${SMTP_USE_SSL:-false}
When this stops being enough¶
- Bulk sends.
send()is synchronous and blocks the request thread. For more than a few emails per request, move to a background queue (tracked as Tier 2 plan #7). - Transactional templates. Right now the API takes raw strings.
If you find yourself building HTML strings inline, that's a sign to
introduce Jinja templates —
scalr-emaildoesn't ship them intentionally to keep the surface tiny. - Per-tenant senders.
SMTP_FROMis platform-wide. If you ever need each tenant's mail to come from their own domain, passsender=per-call (the kwarg already exists) and source it from the tenant's record.
Testing locally¶
For local development without a real SMTP provider, run a fake SMTP server in a container:
docker run --rm -p 1025:1025 -p 8025:8025 mailhog/mailhog
Then in .env.shared:
SMTP_HOST=host.docker.internal
SMTP_PORT=1025
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM=test@scalr.local
SMTP_USE_TLS=false
MailHog's web UI at http://localhost:8025 shows every email your
apps send without actually delivering anything. Useful for working on
email content without burning your provider's free tier.