For the complete documentation index, see llms-docs.txt or the site index llms.txt. Full docs corpus: llms-full-docs.txt. Prefer the markdown version of this page at /docs/setup/backend/email.md. Product capabilities: skill.md. Docs MCP: /docs/mcp. Site MCP: /mcp.
Set up, configure, and run the Reloop platform email microservice locally.
Platform transactional emails triggered by NATS events (invites, OTPs, billing alerts).
Overview
| Property | Value |
|---|---|
| Directory | apps/backend/email |
| Port | 8022 |
| Local URL | https://local.reloop.sh/api/email |
| Swagger UI | https://local.reloop.sh/api/email/openapi |
| Stack | ElysiaJS · Redis · NATS |
Quick start
bun be:email:dev
Or bun backend:dev / bun dev. In development, when neither RELOOP_API_KEY nor SMTP_HOST is set, mail falls back to Mailpit SMTP (localhost:1025). That fallback is development-only: see How system email is delivered.
Environment
apps/backend/email/.env (from bun setup / bun env:setup).
EMAIL_PORT=8022
PORT=8022
BASE_URL="https://local.reloop.sh"
NATS_URL=nats://localhost:4222
RELOOP_API_KEY=
RELOOP_SENDER_DOMAIN=
ONBOARDING_TEST_DOMAIN=
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_SECURE=false
NODE_ENV=development
| Variable | Required | Default | Notes |
|---|---|---|---|
EMAIL_PORT / PORT | YES | 8022 | Listening port |
BASE_URL | YES | https://local.reloop.sh | Platform base URL |
NATS_URL | YES | nats://localhost:4222 | Event subscriptions |
RELOOP_API_KEY | One of | - | API key for the org that owns the sender domain(s); used by reloop-email |
RELOOP_SENDER_DOMAIN | With API key | - | System product mail From domain (auth, billing, invites) |
ONBOARDING_TEST_DOMAIN | Prod | - | Onboarding “Send email” From domain only (can differ) |
SMTP_HOST | One of | - | Relay system mail through your own SMTP server instead of Reloop |
SMTP_PORT | No | 587 | Use 465 with SMTP_SECURE=true |
SMTP_USER | No | - | Omit for an unauthenticated relay |
SMTP_PASSWORD | No | - | Paired with SMTP_USER |
SMTP_SECURE | No | false | true for implicit TLS |
MAILPIT_HOST | No | localhost | Development capture host |
MAILPIT_PORT | No | 1025 | Development capture port |
APP_NAME | No | Reloop | Company name shown as Self-hosted Reloop × <APP_NAME> in system email senders, subjects and bodies |
How system email is delivered
System email is anything Reloop sends to your own users: sign-in codes, invitations, billing alerts. It is separate from customer mail, which always goes through mail.
The service picks a transport at startup, in this order:
| Condition | Transport |
|---|---|
RELOOP_API_KEY is set | Reloop API, sending from RELOOP_SENDER_DOMAIN |
otherwise SMTP_HOST is set | That SMTP server directly |
otherwise, NODE_ENV is not production | Mailpit on MAILPIT_HOST:MAILPIT_PORT |
| otherwise | Nothing. The send fails and logs why. |
The chosen transport is logged once on boot, so reloop logs email tells you
which one is live:
System email transport: SMTP smtp.acme.test:587 (authenticated)
Choosing a transport
Through Reloop. Best once you have a verified sending domain. Create an API
key in the organization that owns it, then set RELOOP_API_KEY and
RELOOP_SENDER_DOMAIN. Your platform mail then gets the same delivery
pipeline, tracking and logs as customer mail.
Through your own SMTP server. Set SMTP_HOST, plus SMTP_USER and
SMTP_PASSWORD if it requires auth. Useful on day one, before any domain is
verified, and for shops that already relay everything through Postmark, SES or
an internal server. Leave RELOOP_API_KEY empty.
SMTP_HOST=smtp.postmarkapp.com
SMTP_PORT=587
SMTP_USER=your-token
SMTP_PASSWORD=your-token
Signing in before email works
A fresh install has no sending domain, so it cannot email you a code to log in
and verify one. Self-host breaks that loop at /dashboard/setup: the installer
prints a one-time setup key, and the wizard creates the first administrator
with a password, so no email is needed. See
VPS install.
DEFAULT_OTP is a development helper and is left empty on new installations.
Installations upgraded from an older installer keep the fixed code they were
given; remove it and restart once a transport above works.
Branding system email
APP_NAME sets the name used across every system email: the sender name, the
subject line and the message body. Any value other than Reloop is shown as
Self-hosted Reloop × <APP_NAME>, so mail carries both your name and the fact
that it runs on Reloop.
APP_NAME=Contoso Mail
Sender with APP_NAME=Contoso Mail | |
|---|---|
| Login code | Self-hosted Reloop × Contoso Mail <auth@…> |
| New sign-in | Self-hosted Reloop × Contoso Mail Security <security@…> |
| Billing | Self-hosted Reloop × Contoso Mail Billing <billing@…> |
| Quota warning | Self-hosted Reloop × Contoso Mail Support <support@…> |
| Invitation | Dana via Self-hosted Reloop × Contoso Mail <invites@…> |
Leave it unset, or set it to Reloop, and everything stays plain Reloop, so
existing deployments are unaffected. The installer defaults it to Reloop and preserves your value
across re-runs.
The sign-in alert previously sent as Security Alert, which was the only
sender that did not carry the product name. It is now <APP_NAME> Security,
matching the Billing and Support senders.
[!NOTE] The email logo is loaded from
BASE_URL, not from Reloop, so replace that asset in your own deployment if you rebrand.
[!NOTE] The Reloop attribution cannot be configured away. The shared footer also keeps Reloop's help link, copyright line and postal address regardless of
APP_NAME.
Commands
| Command | Description |
|---|---|
bun be:email:dev | Start dev server with hot reloading |
bun run --filter=be-email build | Compile production bundle |
bun run --filter=be-email start | Run compiled production build |
bun run --filter=be-email check-types | TypeScript type-check |
Architecture
| Layer | Detail |
|---|---|
| Triggers | Subscribes to NATS subjects (auth.*, billing.*, etc.) |
| Delivery | Sends via the mail pipeline using RELOOP_API_KEY when set |
| Schema | email_log in packages/db/src/schema/email.ts |
Next
Was this page helpful?