Skip to content

Per-login ForwardEmail accounts with From check - #1

Merged
Skeyelab merged 1 commit into
mainfrom
per-login-accounts
Oct 16, 2026
Merged

Skeyelab merged 1 commit into
mainfrom
per-login-accounts

Conversation

@Skeyelab

Copy link
Copy Markdown
Member

What

Lets each SMTP login map to its own ForwardEmail alias credentials, so apps sharing the bridge can send as different addresses without a domain catch-all password.

Config

New optional BRIDGE_ACCOUNTS, a JSON list:

[{"smtp_username": "glitchtip", "smtp_password": "...", "forwardemail_username": "alerts@example.com", "forwardemail_password": "..."}]
  • The existing SMTP_USERNAME/SMTP_PASSWORD/FORWARDEMAIL_USERNAME/FORWARDEMAIL_PASSWORD keep working exactly as before. If both are set, the single-login variables become one more account, so the live deployment needs no config change and can add logins later.
  • Startup fails with a clear message on: partly set single-login variables, invalid JSON, a non-list or empty list, an entry missing a key, an empty value, an unknown key (catches typos like smtp_pasword), or duplicate SMTP usernames across both sources. Messages name the index and key, never the values.

Behavior

  • AUTH finds the matching account (constant-time hmac.compare_digest on bytes, checked against every account with no early exit) and returns it as auth_data, which aiosmtpd stores on the session. Failures still return AuthResult(success=False, handled=False).
  • handle_DATA relays with that session's account credentials. ForwardEmailRelay is now one shared client that takes credentials per send.
  • Before relaying, the From header must be exactly one address equal (case-insensitive) to the account's alias. Otherwise: 553 From address must be <alias> for this login, a permanent error, instead of ForwardEmail's 403 becoming a 451 that apps retry forever. MAIL FROM (envelope/bounce address) is not checked.
  • ForwardEmail failures are still 451, replies still one sanitized line, and Account hides both passwords from repr.

Tests

62 passing (was 24). New coverage: legacy config, multi-account config, combined sources, duplicate and malformed config, per-session credential selection over a real socket, From mismatch (wrong address, look-alike domain, two addresses, missing From) as 553, case and display-name matching, cross-account password and unknown user as 535, non-UTF-8 login as 535.

Each SMTP login can now map to its own ForwardEmail alias credentials via
BRIDGE_ACCOUNTS (a JSON list), so apps sharing the bridge can send as
different addresses without a catch-all password. The single-login
variables keep working unchanged and can be combined with BRIDGE_ACCOUNTS.

- AUTH records the matched account on the session; the message is relayed
  with that account's credentials.
- The From header must be that account's alias (case-insensitive), else a
  permanent 553 naming the expected address instead of a 451 retry loop.
- Passwords are compared in constant time, as bytes.
- Bad config (missing/empty/unknown keys, invalid JSON, duplicate SMTP
  usernames) fails at startup without echoing values.

Assisted-by: Claude Code:claude-opus-5-5
@Skeyelab
Skeyelab merged commit 8fe609a into main Oct 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant