Built-in SMTP server
Lots of self-hosted apps (Synology DSM, Vaultwarden, Forgejo, Pi-hole, Home Assistant) can only send notifications by email. atreoAGENT ships with a small SMTP server that turns those emails into encrypted push notifications. Point an app at it, send mail to a member’s atreoLINK email address, and the agent forwards it as a notification.
The server is off by default and designed for LAN-only use.
Enable it
Section titled “Enable it”In config.yaml:
smtp: enabled: trueOr with environment variables:
SMTP_ENABLED=trueThat’s all that’s required. The gateway listens on 0.0.0.0:2525 by default; it stays LAN-only because the source-IP allowlist rejects anything outside your local networks. Binding to a specific LAN IP (listen: "192.168.1.10:2525" / SMTP_LISTEN=192.168.1.10:2525) is optional defense-in-depth, not a substitute for the allowlist — host-networked containers can’t always rely on a host loopback bind.
Then restart the agent: docker compose restart atreoagent. You’ll see smtp: listening on 0.0.0.0:2525 (full-email routing, AUTH PLAIN/LOGIN required, password = notify API key, STARTTLS disabled (plaintext only)) in the logs.
Source-IP allowlist
Section titled “Source-IP allowlist”The gateway only accepts connections from a configurable list of CIDRs. A connection from a source IP outside the list is rejected before AUTH — the password is never even solicited. This, not the bind address, is what keeps the gateway LAN-only even though it listens on 0.0.0.0:2525 by default.
When smtp.trusted_networks is unset, the allowlist defaults to loopback, the private (RFC 1918) ranges, link-local, IPv6 ULA, and the WireGuard tunnel subnet:
127.0.0.0/8 ::1/12810.0.0.0/8 172.16.0.0/12 192.168.0.0/16169.254.0.0/16 fe80::/10 fc00::/7100.64.0.0/24 (the WireGuard tunnel subnet)To override the list, in config.yaml:
smtp: enabled: true trusted_networks: - "127.0.0.0/8" - "192.168.50.0/24"Or with an environment variable (comma-separated):
SMTP_TRUSTED_NETWORKS=127.0.0.0/8,192.168.50.0/24Setting smtp.trusted_networks replaces the default list, so include loopback and any ranges you still need. If you widen the list to include public IP space, the agent logs a warning at startup — the gateway is designed for LAN use only.
Authentication
Section titled “Authentication”The gateway requires SMTP AUTH. Two mechanisms are advertised: PLAIN and LOGIN (used by older clients).
| Field | Value |
|---|---|
| Username | anything (the agent ignores it) |
| Password | your notification API key (same key the HTTP notify API uses) |
There’s a single shared secret across both the HTTP API and the SMTP gateway. Rotating the notification API key from the dashboard rotates it for both surfaces; there’s nothing extra to update SMTP-side.
Read or rotate the key from app.atreolink.com → your server’s Settings → Notification API key, or from the host:
docker exec atreoagent cat /var/lib/atreoagent/notify_api_keySTARTTLS (optional)
Section titled “STARTTLS (optional)”Some apps (notably Grafana) refuse to send AUTH over a plaintext SMTP connection. For those, enable STARTTLS so the AUTH exchange is encrypted in transit:
smtp: enabled: true listen: "192.168.1.10:2525" tls_enabled: trueOr with an environment variable:
SMTP_TLS_ENABLED=trueOn first start the agent generates a self-signed certificate and persists it under the data dir as smtp_tls_cert.pem + smtp_tls_key.pem. Subsequent starts reuse the same cert. The cert isn’t pinned to a hostname — the LAN bind varies per deployment — so calling apps must skip certificate verification (Grafana: skip_verify = true; most other SMTP clients have an equivalent toggle).
The STARTTLS listener requires TLS 1.3, so the calling app must support TLS 1.3.
This is opportunistic encryption, not server authentication. AUTH (password = notify API key) is what proves you’re talking to the right server.
How routing works
Section titled “How routing works”The agent matches the SMTP RCPT TO address against members’ atreoLINK account emails. To send a notification to a particular member, use their atreoLINK email as the recipient:
From: sonarr@home.example.comTo: alice@example.com <- Alice's atreoLINK account emailSubject: Episode downloadedIf no member matches, the SMTP server returns 550 unknown recipient and drops the message. Matching is case-insensitive and whitespace-trimmed.
Catch-all recipient
Section titled “Catch-all recipient”By default, mail addressed to anything that isn’t a member’s email is rejected at RCPT TO. Set smtp.catch_all to a fallback recipient and that mail is delivered to them instead of bouncing:
smtp: enabled: true catch_all: "alerts@example.com" # an existing member's atreoLINK emailOr with an environment variable:
SMTP_CATCH_ALL=alerts@example.comThe catch-all address must belong to an existing member. A push notification is end-to-end encrypted (sealed-box) to a member’s identity key, so a member is the only address the agent can actually deliver to. If smtp.catch_all is set to an address that isn’t a member, unmatched mail is still rejected with 550 and the agent logs a warning — a misconfigured catch-all is surfaced, not silently dropped.
When a message is delivered through the catch-all, the address the sender originally used is surfaced in the notification’s From line, rendered as original-sender → original-recipient, so the receiving member can tell which mailbox the mail actually targeted:
From: grafana@home.example.com → oncall@example.comCatch-all matching is case-insensitive and whitespace-trimmed, the same as normal recipient routing.
Limits
Section titled “Limits”| Limit | Default | Override |
|---|---|---|
| Max message size | 1 MiB | SMTP_MAX_MESSAGE_BYTES |
| Rate per source IP | 50 / minute | SMTP_RATE_PER_MINUTE |
| Recipients per message | 1 | not configurable |
| Attachments | rejected | not configurable |
The SMTP gateway also shares the per-recipient notification cap with the HTTP API: 60 notifications per minute per member, summed across every server and every paired device. Mail that would push a recipient over that line is rejected at delivery time. See Rate limits.
Multipart messages (text/html + text/plain) are forwarded as-is.
Configure your apps
Section titled “Configure your apps”Most self-hosted apps with an SMTP setting need:
| Field | Value |
|---|---|
| Host | the agent server’s LAN IP (e.g. 192.168.1.10) |
| Port | 2525 |
| Encryption | None — or STARTTLS if you’ve enabled it |
| Authentication | AUTH PLAIN or AUTH LOGIN |
| Username | anything (e.g. atreoagent) |
| Password | your notification API key |
| From | anything (not validated) |
| To | the member’s atreoLINK account email |
Synology DSM example
Section titled “Synology DSM example”In Control Panel → Notification → Email:
Outgoing email service Custom SMTP serverSMTP server 192.168.1.10SMTP port 2525Authentication required onUsername atreoagentPassword <your notification API key>Send email as dsm@home.example.comEnter the member’s atreoLINK email under Recipient’s email in the System tab.
What ends up in the notification
Section titled “What ends up in the notification”| Email field | Notification field |
|---|---|
| Subject | Title |
| Body (HTML if present, plaintext otherwise) | Body |
| HTML part | HTML view in the inbox |
| Plaintext part | Plaintext view in the inbox |
From: header | Sender metadata, shown in the inbox |
Subject: header | Subject metadata, shown in the inbox |
Notifications are encrypted to the recipient’s atreoLINK identity key before atreoAGENT relays them. The relay never sees plaintext.
Troubleshooting
Section titled “Troubleshooting”535 5.7.8 authentication failed. The password didn’t match. Check the notification API key in the dashboard; if you’ve rotated it recently, update the SMTP configuration on the calling app.
530 5.7.0 authentication required. The app sent MAIL FROM without authenticating first. Enable AUTH in the app’s SMTP settings.
550 unknown recipient. The recipient address doesn’t match any member’s atreoLINK email. Check the email address (not the display name). If you’ve set a catch-all and still see this, the smtp.catch_all value itself isn’t a member’s email — the agent logs a warning naming the offending address.
452 too many messages. You hit the per-source-IP rate limit. Raise SMTP_RATE_PER_MINUTE or batch fewer messages.
554 attachments not supported. Strip attachments at the source. The bridge is for notifications, not for delivering files.
Mail accepted, no notification. Check the agent logs for dispatch failed. The most common cause is that the recipient hasn’t paired a phone or browser yet, so there’s no destination for the notification.
© 2026 atreoLABS. All rights reserved.
WireGuard is a registered trademark of Jason A. Donenfeld.

