---
title: Email
icon: mail
description: Set up, configure, and run the Reloop platform email microservice locally.
---
> For the complete documentation index, see [llms-docs.txt](/llms-docs.txt) or the site index [llms.txt](/llms.txt). Full docs corpus: [llms-full-docs.txt](/llms-full-docs.txt). Prefer markdown URLs (append `.md`) for agent consumption. Product skill: [skill.md](/skill.md).


Platform transactional emails triggered by NATS events (invites, OTPs, billing alerts).

<Tip>
Run [`bun setup`](/docs/setup) once from the monorepo root. Env files come from `bun setup` / `bun env:setup`.
</Tip>

<Warning>
**Email** ≠ **Mail**. Email sends platform notifications to Reloop users (invites, password resets). [Mail](/docs/setup/backend/mail) is the customer-facing send API for their audiences.
</Warning>


## 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

```bash
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](#how-system-email-is-delivered).


## Environment

`apps/backend/email/.env` (from `bun setup` / `bun env:setup`).

```bash
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](/docs/setup/backend/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)
```

<Warning>
In production, Mailpit is never used. Earlier versions fell back to it
silently, so an install without `RELOOP_API_KEY` accepted sign-ins and then
dropped every one-time code into a Mailpit that was not running. The service
now refuses to send and logs the reason instead.
</Warning>

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

```bash
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](/docs/self-host/vps).

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

```bash
APP_NAME=Contoso Mail
```

| Email | 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](/docs/setup/backend/mail) pipeline using `RELOOP_API_KEY` when set |
| **Schema** | `email_log` in `packages/db/src/schema/email.ts` |


## Next

<CardGroup cols={2}>
  <Card icon="rocket" title="Setup" href="/setup">
    One-command local bootstrap
  </Card>
  <Card icon="mail-single" title="Mail" href="/setup/backend/mail">
    Customer send API (different service)
  </Card>
  <Card icon="lock" title="Auth" href="/setup/backend/auth">
    Triggers invites and OTP emails
  </Card>
  <Card icon="globe" title="Port reference" href="/setup/port">
    Gateway routes and ports
  </Card>
</CardGroup>
