You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This pull request introduces the foundational database schema and model changes necessary to support a full-featured mail hosting stack, including per-domain mail enablement, DKIM key management, per-user mail accounts, unsubscribe handling, and agent mail service reporting. The changes are primarily new Sequelize migrations and model updates, establishing all core tables, relationships, and constraints for the mail system.
Database schema for mail hosting:
Added columns to ExternalDomains for mail enablement, DNS verification states, and last check results, enabling per-domain mail send/receive control and status tracking. (create-a-container/migrations/20261008000000-add-external-domain-mail-columns.js, create-a-container/models/external-domain.js) [1][2]
Created the DkimKeys table and model for per-domain DKIM key pairs, including status and selector, with associations to ExternalDomain. (create-a-container/migrations/20261008000001-create-dkim-keys.js, create-a-container/models/dkim-key.js, create-a-container/models/external-domain.js) [1][2][3]
Added the MailAccounts table for per-user mailboxes, with references to both Users and ExternalDomains, including quota, password hash, and unsubscribe header settings. (create-a-container/migrations/20261008000002-create-mail-accounts.js)
Introduced the MailSuppressions and MailUnsubscribeKeys tables to support one-click unsubscribe, with suppression tracking per account and AES key management for secure tokens. (create-a-container/migrations/20261008000004-create-mail-suppressions.js, create-a-container/migrations/20261008000005-create-mail-unsubscribe-keys.js) [1][2]
Created SQL views and migrations for the mail stack (Dovecot/Postfix) to read live mailbox and suppression data, and established read-only DB roles (mail_dovecot, mail_postfix) with appropriate grants. (create-a-container/migrations/20261008000006-create-mail-views.js, create-a-container/migrations/20261012000000-create-mail-db-roles.js) [1][2]
Added a migration to backfill DKIM keys for existing domains. (create-a-container/migrations/20261008000007-backfill-dkim-keys.js)
Agent and application integration:
Extended the Agents table and model with fields for enabled services, missing binaries, mail-host claim, locality, and API key pinning, supporting mail service reporting and the single-mail-host invariant. (create-a-container/migrations/20261008000003-add-agent-mail-fields.js, create-a-container/models/agent.js) [1][2]
Registered a new /u route for public one-click unsubscribe, mounted before CSRF protection for compatibility with mail clients. (create-a-container/app.js)
These changes lay the groundwork for robust mail hosting, security, and management features, and are designed for compatibility with both local and remote mail hosts.
…ibe (#67)
Split 1/4 of #495. Adds the manager-side mail foundation:
- DB migrations, models, SQL views, and mail DB roles
- /api/v1/mail-accounts resource with suppressions
- Per-domain mail enablement, DKIM keys, and Check DNS
- Mail-host claim and agent snapshot mail section
- One-click unsubscribe (keys, tokens, public /u route)
- OpenAPI spec for mail endpoints
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add AES-256-GCM token creation and verification for #67, concealing account and recipient data and authenticating the key ID. Allow the VS Code SQLite dependency's install scripts.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved critical issues can expose mail secrets over plaintext HTTP, authorize renamed domains using stale DNS verification, accept invalid SPF configurations, and erase mail settings when saving the existing Settings page.
The reason will be displayed to describe this comment to others. Learn more.
I Kept the dedicated route /external-domains admin-only, while any authenticated user needs the sendable-domain list (id + name only) to create a mail account. Folding it in would either expose admin data or break non-admin account creation.
- Stateless unsubscribe keys: drop the MailUnsubscribeKeys table/model/
migration; AES keys are now HKDF-derived from the server's session
secrets (newest mints, older ones keep old links verifying)
- Fold /mail/enable + /mail/disable into PATCH /external-domains/:id
with mailEnabled (enable keeps the Check DNS gate)
- Fold /api/v1/mail into the agents resource: GET/DELETE /agents/mail-host
and GET /agents/mail-host/ptr (per-route csrfGuard — the agents router
mounts before the app-level guard)
- Move the unsubscribe pages into views/unsubscribe/*.ejs (ejs dependency)
- Replace hand-rolled CIDR math with Node's net.BlockList; TODO points at
mailauth for full RFC 7208 SPF evaluation
- Inline the mail view SQL into migration 20261008000006; delete
utils/mail-views.js
- Drop the password 'warning' string from create/rotate responses (UI copy)
- Comment the isLocal agent field; TODO for folding smtp_url/
smtp_noreply_address into the mail system
- Remove stray mail-helper/src/unsubscribe-token.js and the accidental
package.json allowScripts entry
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Mail-host endpoints moved under agents: GET/DELETE /api/v1/agents/mail-host,
GET /api/v1/agents/mail-host/ptr
- Enable/disable mail is now PATCH /external-domains/:id { mailEnabled }
- One-time password warning copy is owned by the UI, not the API response
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved critical flaws allow mail authorization without DNS verification, retain suspended users’ mail access, bypass recipient suppressions through account recreation, and split mailbox data during automatic host failover.
RFC 8058 §3.2 permits—and recommends—multipart/form-data unsubscribe POSTs, but express.urlencoded() does not parse that encoding. Such a request reaches the body check without List-Unsubscribe and returns 400 even with a valid token, so compliant mail clients cannot unsubscribe. Add a bounded multipart field parser for this endpoint alongside the URL-encoded parser.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Unresolved critical defects can erase mail configuration during settings saves, enable renamed domains without DNS verification, and allow admin sessions to bypass pinned-agent API-key checks.
A remote admin session bypasses the agent's API-key pin because apiAuth accepts the session without setting req.apiKey, and this condition then skips the check. A different admin API key can obtain such a session through another authenticated endpoint and use it to impersonate the pinned agent. Require a matching API key for remote check-ins to pinned agents, rejecting an absent key as well as a mismatched one.
Packaged installs omit templates required by unsubscribe pages
create-a-container/routers/unsubscribe.js:36
The production package does not include these templates: create-a-container/Makefile copies only APP_DIRS, which does not contain views. Consequently, every unsubscribe page render—including the invalid-link response—fails with ENOENT and returns 500 on packaged installations, even though it works from the checkout. Add views to the package's installation inputs so the public endpoint works after deployment.
Multipart one-click unsubscribe requests are not parsed
create-a-container/routers/unsubscribe.js:31
RFC 8058 receivers may send the one-click POST as multipart/form-data, but neither this parser nor the app-level JSON/URL-encoded parsers handle that content type. Such a request leaves req.body empty and receives a 400 even with a valid token and a List-Unsubscribe=One-Click form field. Add bounded multipart field parsing alongside URL-encoded parsing so these unsubscribe requests succeed.
Update logic to retain degraded holder's claim for Maildirs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Saving unrelated settings can erase mail configuration, while stale domain verification and incorrect SPF validation can enable sending for unverified or misconfigured domains.
- Reset DNS verification (flags + check metadata) whenever a domain is
renamed, in both PUT and PATCH, and run the mailEnabled=true Check DNS
against the name being saved so a rename cannot enable mail using the
old name's DNS result.
- Preserve omitted mail settings on PUT /settings: mail keys are written
only when present in the request (explicit empty string still clears),
and the relayhost/SPF-include rule validates the effective saved pair.
- Soft-delete mail accounts (paranoid) so MailSuppressions survive
delete + recreate of the same address: recreation restores the same
row/id, keeping its opt-outs; the account SQL views exclude
soft-deleted rows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Deleted-account restoration can grant another user access to the previous owner’s retained mailbox and leave old credentials active if replacement fails.
Refactor restore function to use a transaction for restoring and updating the account.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Concurrent initial PostgreSQL claims can fail with a 500 instead of returning a conflict snapshot, and migration rollback silently leaves both login roles and their grants behind.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SMTP PR (1/4)
This pull request introduces the foundational database schema and model changes necessary to support a full-featured mail hosting stack, including per-domain mail enablement, DKIM key management, per-user mail accounts, unsubscribe handling, and agent mail service reporting. The changes are primarily new Sequelize migrations and model updates, establishing all core tables, relationships, and constraints for the mail system.
Database schema for mail hosting:
ExternalDomainsfor mail enablement, DNS verification states, and last check results, enabling per-domain mail send/receive control and status tracking. (create-a-container/migrations/20261008000000-add-external-domain-mail-columns.js,create-a-container/models/external-domain.js) [1] [2]DkimKeystable and model for per-domain DKIM key pairs, including status and selector, with associations toExternalDomain. (create-a-container/migrations/20261008000001-create-dkim-keys.js,create-a-container/models/dkim-key.js,create-a-container/models/external-domain.js) [1] [2] [3]MailAccountstable for per-user mailboxes, with references to bothUsersandExternalDomains, including quota, password hash, and unsubscribe header settings. (create-a-container/migrations/20261008000002-create-mail-accounts.js)MailSuppressionsandMailUnsubscribeKeystables to support one-click unsubscribe, with suppression tracking per account and AES key management for secure tokens. (create-a-container/migrations/20261008000004-create-mail-suppressions.js,create-a-container/migrations/20261008000005-create-mail-unsubscribe-keys.js) [1] [2]mail_dovecot,mail_postfix) with appropriate grants. (create-a-container/migrations/20261008000006-create-mail-views.js,create-a-container/migrations/20261012000000-create-mail-db-roles.js) [1] [2]create-a-container/migrations/20261008000007-backfill-dkim-keys.js)Agent and application integration:
Agentstable and model with fields for enabled services, missing binaries, mail-host claim, locality, and API key pinning, supporting mail service reporting and the single-mail-host invariant. (create-a-container/migrations/20261008000003-add-agent-mail-fields.js,create-a-container/models/agent.js) [1] [2]/uroute for public one-click unsubscribe, mounted before CSRF protection for compatibility with mail clients. (create-a-container/app.js)These changes lay the groundwork for robust mail hosting, security, and management features, and are designed for compatibility with both local and remote mail hosts.