From cfbc240f985e15aa9303a78e085c235e72708ce7 Mon Sep 17 00:00:00 2001 From: Ben Fairless Date: Tue, 15 Sep 2026 13:40:00 +0800 Subject: [PATCH 1/5] Add routing, mail server settings and sending domains pages New feature pages written from the 3.3.7 source: - Routing incoming e-mail: MX vs forwarding address, route name rules, the five route modes and their SMTP-time behaviour, additional endpoints, processing order, HTTP/SMTP/address endpoint options (including the STARTTLS/STARTLS mismatch), retries and bounces - Mail server settings: Live/Development, retention behaviour and defaults, send limits (rolling hour, 90% warning, hourly notices), every reason a message can be held and whether release bypasses it, suppression list rules, advanced/admin-only settings, suspension, deletion - Sending domains: org vs server domains, DNS and e-mail verification, the four DNS checks with exact expected records and statuses, DKIM signing and fallback, custom return path for DMARC alignment, use_for_any --- content/3.features/mail-server-settings.md | 101 +++++++++++++++++ content/3.features/routing-incoming-email.md | 95 ++++++++++++++++ content/3.features/sending-domains.md | 113 +++++++++++++++++++ 3 files changed, 309 insertions(+) create mode 100644 content/3.features/mail-server-settings.md create mode 100644 content/3.features/routing-incoming-email.md create mode 100644 content/3.features/sending-domains.md diff --git a/content/3.features/mail-server-settings.md b/content/3.features/mail-server-settings.md new file mode 100644 index 0000000..d93c72b --- /dev/null +++ b/content/3.features/mail-server-settings.md @@ -0,0 +1,101 @@ +--- +title: Mail Server Settings +description: 'Modes, limits, retention, held messages, the suppression list and other per-server settings.' +category: Features +--- + +Each mail server within an organization has its own settings, found under **Settings** in the server menu. Some settings are only visible to [global administrators](/features/users-and-permissions) under **Advanced Settings**. + +## Server settings + +| Setting | Description | +|---|---| +| **Name** | A display name, unique within the organization. | +| **Permalink** | A short identifier (letters, digits and hyphens) used in SMTP usernames (`org-permalink/server-permalink`) and URLs. It cannot be changed after the server is created. | +| **Mode** | `Live` or `Development`. See below. | +| **IP pool** | Only shown when [IP pools](/features/ip-pools) are enabled. The pool that outgoing mail from this server is sent from unless an IP pool rule matches. | +| **Postmaster** | The contact address included in bounce messages Postal generates when an incoming message cannot be delivered. Defaults to `postmaster@` the message's domain. | + +### Live and Development mode + +In **Live** mode all mail is routed normally. In **Development** mode every outgoing and incoming message is placed in the held queue with the note "Server is in development mode." instead of being delivered to recipients or endpoints. Messages are still parsed, inspected and visible in the web interface, and count towards the server's send limit. Individual held messages can be released manually from the web interface, which delivers them despite the mode. + +If you only want to hold messages from a particular application or environment rather than the whole server, set the **hold** option on that application's [credential](/features/smtp-authentication#holding-messages-from-a-credential) instead. + +## Spam + +The **Spam threshold** and **Spam failure threshold** for incoming mail are set here. See [Spam & Virus Checking](/features/spam-and-virus-checking#classifying-spam). + +## Retention + +Each server has three retention settings which are shown on the **Retention** page and can be changed by a global administrator under **Advanced Settings**. They are enforced by a background task which runs once a day at 03:00 (server time, normally UTC). + +| Setting | Default | Description | +|---|---|---| +| **Raw message retention days** | 30 | How many days the raw content of messages (headers, bodies and attachments) is kept. Raw data is stored in one table per day, and whole days are removed once they are older than this. After removal the message still appears in lists and searches but its content and attachments can no longer be viewed, and a queued message whose raw data has been removed will fail with "Raw message has been removed". | +| **Raw message retention size** | 2048 MB | The total disk space raw message data may use. When exceeded, whole days are removed starting with the oldest until usage is under the limit. | +| **Message retention days** | 60 | How many days message metadata (the message record itself, its deliveries, clicks, loads and spam checks) is kept. Older messages are deleted entirely. | + +Leaving any of these blank disables that limit ("Indefinitely" / "No limit"). The **Retention** page also shows the current total size of the server's message database. + +## Send limit + +A global administrator can set a **Send limit** for a server under **Advanced Settings**. This is the maximum number of outgoing messages accepted in a rolling 60 minute window; the current usage is shown on the **Send Limit** page. Incoming messages are counted but not limited. + +* When the volume reaches **90%** of the limit, the server is marked as *approaching* its limit. +* When the volume reaches the limit, every further outgoing message is held with the note "Message held because send limit (N) has been reached." until the volume drops. Releasing a held message while the server is still over the limit holds it again. + +Once a minute Postal checks for servers that have recently approached or exceeded their limit and, at most once per hour for each state, e-mails every user in the organization and triggers the `SendLimitApproaching` / `SendLimitExceeded` [webhook events](/developer/webhooks#send-limit-events). The e-mails are sent using the `smtp` section of your Postal configuration. + +## Held messages + +Messages that are held are not delivered but remain visible under **Messages → Held** where they can be released (re-queued for delivery) or the hold cancelled. Each held message triggers a `MessageHeld` webhook. A message may be held for any of the following reasons: + +| Reason | Details recorded | Can be released manually? | +|---|---|---| +| Server is in Development mode | "Server is in development mode." | Yes | +| Credential is set to hold | "Credential is configured to hold all messages authenticated by it." | Yes | +| Recipient is on the suppression list | "Recipient (…) is on the suppression list (reason: …)" | Yes | +| Send limit reached | "Message held because send limit (…) has been reached." | Only once the volume has dropped below the limit | +| Server or organization is suspended | "Mail server has been suspended…" | No - it will be held again until unsuspended | +| Incoming spam on a route set to Quarantine | "Message placed into quarantine." | Yes | +| Incoming mail on a route set to Hold | "Message has been accepted but not sent to any endpoints." | Yes (marked as Processed) | + +Held messages expire after the number of days set by `postal.default_maximum_hold_expiry_days` (default **7**). An hourly task cancels the hold on expired messages, recording a `HoldCancelled` delivery with the note "The hold on this message has been removed without action." The message is not delivered. + +## Suppression list + +Each server maintains a suppression list of recipient addresses that Postal will not deliver to, viewable under **Messages → Suppressions**. Addresses are added automatically when Postal is sending **outgoing** mail: + +* **Too many hard fails** - a permanent (`5xx`) rejection from the recipient's mail server when there has already been at least one other hard fail to the same address in the previous 24 hours. +* **Too many soft fails** - the message has been retried the maximum number of times (`postal.default_maximum_delivery_attempts`, default 18) without success. + +Incoming bounce messages do **not** add addresses to the list. + +While an address is on the list, new messages to it are held (see above) rather than attempted. Releasing such a message manually bypasses the list, and if the delivery then succeeds the address is removed from the list. Entries are otherwise removed automatically after `postal.default_suppression_list_automatic_removal_days` (default **30**) days. + +## Advanced settings (administrators only) + +| Setting | Description | +|---|---| +| **Send limit** | See above. | +| **Allow sender header** | Permits any `From` address as long as a `Sender` header contains an address on one of the server's verified domains. See [From/Sender validation](/features/smtp-authentication#fromsender-validation). Applies to SMTP and the API. | +| **Privacy mode** | When enabled, the `Received` header Postal adds to submitted messages omits the submitting client's IP address, reverse DNS hostname and HELO name, leaving only `by {hostname} with SMTP/HTTP; {date}`. | +| **Log SMTP data** | Log the full content of messages submitted using this server's credentials in the SMTP server log. Debugging only. See [Logging](/features/logging#smtp-server-logging). | +| **Outbound spam threshold** | Enables spam scanning of outgoing messages; messages scoring at or above this are failed. Blank disables outbound scanning. See [Spam & Virus Checking](/features/spam-and-virus-checking). | +| **Message retention days**, **Raw message retention days**, **Raw message retention size** | See [Retention](#retention). | + +### Suspending a server + +An administrator can **suspend** a server from **Advanced Settings** by entering a reason. While suspended: + +* Every message processed for the server (incoming and outgoing) is held with the reason "Mail server has been suspended". +* The SMTP server rejects `RCPT TO` for the server's routes and credentials with `535 Mail server has been suspended`. +* API requests using the server's credentials return the `ServerSuspended` error. +* All users in the organization are e-mailed about the suspension. + +Organizations also carry a suspension flag which suspends all of their servers at once; there is no interface for this, but it can be set from `postal console` (`Organization.find_by(permalink: "my-org").update(suspended_at: Time.now)`). Use **Unsuspend server** to restore normal operation; held messages must then be released manually. + +## Deleting a server + +Deleting a server (**Settings → Delete**, which requires typing the server's name to confirm) marks it as deleted immediately and hides it from the interface. The server's message database is dropped by an hourly background task shortly afterwards. This cannot be undone. diff --git a/content/3.features/routing-incoming-email.md b/content/3.features/routing-incoming-email.md new file mode 100644 index 0000000..5dc242b --- /dev/null +++ b/content/3.features/routing-incoming-email.md @@ -0,0 +1,95 @@ +--- +title: Routing Incoming E-Mail +description: 'Routes, endpoints and what happens to mail that arrives at your Postal server.' +category: Features +--- + +As well as sending mail, each Postal mail server can receive mail for the domains you have added to it. Incoming mail is matched to a **route**, and each route decides what should happen to the message - most commonly delivering it to an **endpoint** such as your application's HTTP URL, another SMTP server or an ordinary e-mail address. + +## Getting mail to Postal + +There are two ways to get incoming mail into Postal. + +**Point your MX records at Postal.** Add the MX records from your `dns.mx_records` configuration (for example `mx1.postal.example.com` and `mx2.postal.example.com`, both at priority 10) to the domain. Postal's [domain DNS checks](/features/sending-domains#dns-checks) will show a green tick once they are visible. All mail for addresses on that domain will then arrive at your Postal SMTP server. + +**Forward mail from an existing mail server.** If the domain already has a mail server, you don't need to change any DNS. Every route has a unique forwarding address of the form `{token}@{route domain}` (e.g. `a1b2c3d4@routes.postal.example.com`), shown in the **Address** field when you edit the route. Forward mail from your existing server to this address and it will be treated exactly as if it had been sent to the route's real address. The route domain must have an MX record pointing at Postal - see [DNS configuration](/getting-started/dns-configuration#route-domain). + +## Routes + +Routes are managed under **Routing → Routes** in the server menu. A route consists of: + +* **Name and domain** - the address to route, e.g. `support` @ `yourdomain.com`. The domain must be a verified domain belonging to the server or its organization. The name may be: + * an ordinary local part (lower case letters, digits, `-` and `.`); + * `*` to receive mail for every address on the domain ([wildcards](/other/wildcards-and-address-tags)); + * `__returnpath__` with no domain, to receive mail sent to the server's return path address ([return path routes](/other/auto-responders-and-bounces#return-path-routes)). Only one of these may exist per server and it must deliver to an HTTP endpoint. + + Mail to `name+anything@domain` also matches the route for `name@domain` ([address tags](/other/wildcards-and-address-tags)). A given name/domain combination can only be routed once across your whole installation. + +* **Endpoint** - either one of the server's endpoints, or one of the special modes below. +* **Additional endpoints** - see below. +* **Spam mode** - `Mark`, `Quarantine` or `Fail`. See [Spam & Virus Checking](/features/spam-and-virus-checking#classifying-spam). + +### Route modes + +The **Endpoint** dropdown also offers four special modes in place of a real endpoint: + +| Mode | At `RCPT TO` time | When processed | +|---|---|---| +| **Endpoint** (an HTTP, SMTP or address endpoint) | Accepted | Delivered to the endpoint. | +| **Accept** | Accepted | Recorded as **Processed** with no delivery. The message is stored and visible in the web interface. | +| **Hold** | Accepted | Placed in the held queue (status **Held**). You can release it from the web interface, after which it is marked **Processed**. Held messages expire after `postal.default_maximum_hold_expiry_days` (default 7). | +| **Bounce** | Accepted | Marked as **HardFail** and a bounce is sent back to the sender explaining the message was not delivered. | +| **Reject** | Rejected with `550 Route does not accept incoming messages` | Never stored. | + +A message is only rejected at SMTP time by the **Reject** mode (and by a suspended server). Everything else is accepted, queued, and processed by a worker. + +### Additional endpoints + +A route with a real endpoint can also deliver the same message to any number of additional endpoints. A separate copy of the message (with its own ID and delivery history) is created for each endpoint. Additional endpoints on a wildcard (`*`) route must be HTTP endpoints. Additional endpoints cannot be used with the Accept, Hold, Bounce or Reject modes. + +### Processing order + +When a worker processes an incoming message it performs the following steps in order: + +1. If the message is a bounce for something Postal sent, link it to the original message and stop ([bounces](/other/auto-responders-and-bounces)). +2. Run spam and virus inspection if enabled. If the score is at or above the server's **spam failure threshold**, hard fail. +3. If the server is in **Development** mode, hold the message. +4. Apply the route's spam mode if the message was marked as spam (Quarantine holds, Fail hard fails). +5. Apply the route mode (Accept, Hold, Bounce or deliver to the endpoint). +6. On a hard failure from an endpoint, send a bounce to the sender (unless the endpoint returned `429`). + +## Endpoints + +Endpoints are created under **Routing** in the server menu and can be shared by any number of routes on that server. Deleting an endpoint changes any routes using it to **Reject**. + +### HTTP endpoints + +Deliver the message to your application as an HTTP `POST`. All options and the payload formats are described on the [Receiving e-mail by HTTP](/developer/http-payloads) page. + +### SMTP endpoints + +Forward the message to another SMTP server. + +| Field | Description | +|---|---| +| **Hostname** | The server to connect to. Postal connects directly to this host (no MX lookup is performed). | +| **Port** | Defaults to `25`. | +| **SSL mode** | `None` - never use TLS. `Auto` - use STARTTLS if the server offers it, without verifying the certificate, falling back to plain text if the TLS handshake fails. `TLS` - connect with implicit TLS (e.g. port 465) and verify the certificate. `STARTTLS` - see the note below. | + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +In Postal 3.3.7 the STARTTLS option on SMTP endpoints does not work as intended due to a mismatch in the source code (the endpoint stores STARTTLS but the SMTP client checks for STARTLS), so it currently behaves the same as None. Use Auto or TLS until this is fixed. +:: + +The message is forwarded with an envelope sender of `{server token}@{return path domain}` so that bounces come back to Postal, and a `Resent-Sender` header is added if `postal.use_resent_sender_header` is enabled. Connection timeouts are controlled by `smtp_client.open_timeout` and `smtp_client.read_timeout` (default 30 seconds each). Temporary (`4xx`) responses are retried; permanent (`5xx`) responses hard fail and cause a bounce. + +### Address endpoints + +Forward the message to an ordinary e-mail address. Postal looks up the MX records for the address's domain (or uses your configured `postal.smtp_relays` if any) and delivers it as it would an outgoing message, with the `RCPT TO` replaced by the target address. Each address may only be added once per server. + +## Delivery, retries and bounces + +Deliveries to endpoints follow the same rules as outgoing mail: temporary failures are retried with an exponential back-off (`5 minutes × 1.3ⁿ`) up to `postal.default_maximum_delivery_attempts` times (default 18, roughly 31 hours), after which the message is hard failed. A hard failure of an incoming message causes a bounce to be sent to the original sender. Each attempt is recorded on the message's **Activity** tab and triggers the corresponding [webhook](/developer/webhooks#message-status-events). + +## Limits + +The SMTP server accepts messages up to `smtp_server.max_message_size` (default 14 MB). Incoming volume is counted in the server's statistics but is not subject to the server's send limit. diff --git a/content/3.features/sending-domains.md b/content/3.features/sending-domains.md new file mode 100644 index 0000000..b8659ed --- /dev/null +++ b/content/3.features/sending-domains.md @@ -0,0 +1,113 @@ +--- +title: Sending Domains +description: 'Adding and verifying the domains you send mail from, and the DNS records Postal checks.' +category: Features +--- + +Before a mail server can send mail from an address, the address's domain must be added to Postal and **verified**. Postal then checks the domain's SPF, DKIM, MX and return path DNS records and shows the results in the web interface so that you can ensure your mail is delivered reliably. + +::callout{icon="i-heroicons-information-circle"} +This page covers the DNS records for domains you send from. The records that your Postal installation itself needs (the return path domain, SPF include, MX hostnames and so on) are described under DNS configuration. +:: + +## Organization and server domains + +Domains can be added in two places: + +* **Organization domains** (organization menu → **Domains**) are available to every mail server in the organization. +* **Server domains** (server menu → **Domains**) are only available to that server. + +The same domain name may be added at both levels or to several servers, each with its own DKIM key and verification. When an outgoing message is authenticated, Postal looks for a verified domain matching the `From` address's domain, preferring a server-level domain over an organization-level one. + +Only the exact domain is matched - to send from `news.yourdomain.com` you need to add `news.yourdomain.com` as well as `yourdomain.com`. + +## Verifying a domain + +When you add a domain you must prove that you control it. Global administrators skip this step and their domains are verified immediately. Other users choose one of two methods: + +**DNS** - add a TXT record at the domain itself (the apex, `@`) with the value shown, which is `postal-verification {token}` (the prefix is set by `dns.domain_verify_prefix`). Then click **Verify TXT record**. The token is a 32 character random string. + +**E-Mail** - Postal sends a 6 digit code to one of `webmaster@`, `postmaster@`, `admin@`, `administrator@` or `hostmaster@` at the domain (or any of its parent domains). Enter the code to complete verification. This requires the `smtp` section of Postal's configuration to be working. + +Unverified domains cannot be used for sending, in routes, or for tracking domains. + +## DNS checks + +Once verified, open the domain (the **DNS Setup** page) to see the records you need to add. Postal checks these records immediately when you press **Check my records are correct**, and re-checks every domain automatically once an hour. The results are shown as ticks and crosses in the domain list. + +By default Postal queries the domain's own authoritative nameservers so that changes are seen without waiting for caches to expire. Set `postal.use_local_ns_for_domain_verification: true` to use the resolvers from `dns.resolv_conf_path` instead. + +### SPF + +A TXT record at the apex of the domain beginning `v=spf1` which includes your installation's SPF include, e.g. + +```text +v=spf1 a mx include:spf.postal.example.com ~all +``` + +| Status | Meaning | +|---|---| +| `OK` | A `v=spf1` record containing `include:{dns.spf_include}` was found. | +| `Missing` | No `v=spf1` record exists. | +| `Invalid` | An SPF record exists but does not include your Postal SPF include. If you already have an SPF record for another service, add `include:spf.postal.example.com` to it rather than creating a second record. | + +### DKIM + +Postal generates a 1024-bit RSA key pair for each domain when it is added. Publish the public key in a TXT record named `{selector}._domainkey.yourdomain.com`, where the selector is `{dns.dkim_identifier}-{6 random letters}` (for example `postal-KJHDSA._domainkey`). The exact name and value are shown on the DNS Setup page and look like: + +```text +v=DKIM1; t=s; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ...; +``` + +| Status | Meaning | +|---|---| +| `OK` | Exactly one TXT record exists and its value matches. | +| `Missing` | No TXT record was returned for the name. | +| `Invalid` | Either more than one TXT record exists at the name, or the value does not match the one provided. Check it has been copied exactly. | + +Outgoing messages from a domain whose DKIM status is `OK` are signed with the domain's key (`d=yourdomain.com`). If the DKIM record is not `OK`, messages are still sent but are signed with the installation's key using `d={dns.return_path_domain}` instead - which is why you must also publish the record from `postal default-dkim-record` at `postal._domainkey.{return path domain}` (see [DNS configuration](/getting-started/dns-configuration#return-path)). + +Signatures use `rsa-sha256` with relaxed canonicalisation and cover the `From`, `Sender`, `Reply-To`, `Subject`, `Date`, `Message-ID`, `To`, `Cc`, `MIME-Version`, `Content-Type`, `Content-Transfer-Encoding`, `Resent-*`, `In-Reply-To`, `References` and `List-*` (including `List-Unsubscribe-Post`) headers where present. + +### Return path + +The **return path** is the SMTP envelope sender (`MAIL FROM`) used for outgoing mail, and it is where bounces are sent. By default it is `{server token}@{dns.return_path_domain}`, a hostname belonging to your Postal installation. This is fine, but because the envelope domain differs from your `From` domain it will not give SPF **alignment** for DMARC. + +To fix this, add a CNAME record at `psrp.yourdomain.com` (the prefix is set by `dns.custom_return_path_prefix`) pointing to your installation's return path domain, e.g. + +```text +psrp.yourdomain.com. CNAME rp.postal.example.com. +``` + +Once the check passes, Postal uses `{server token}@psrp.yourdomain.com` as the envelope sender for mail from this domain. Because it is a CNAME, the SPF and DKIM records you published for the return path domain during installation apply automatically, and Postal's SMTP server accepts bounces for any domain beginning with the custom return path prefix. + +| Status | Meaning | +|---|---| +| `OK` | A single CNAME pointing at `{dns.return_path_domain}` was found. Postal will use the custom return path. | +| `Missing` | No record exists. Postal uses the default return path. This is acceptable but not recommended. | +| `Invalid` | A record exists but does not point at the right hostname. | + +### MX + +MX records are only needed if you want to **receive** mail for the domain through Postal (see [Routing incoming e-mail](/features/routing-incoming-email)). Postal checks that every hostname listed in `dns.mx_records` appears among the domain's MX records (case-insensitively). + +| Status | Meaning | +|---|---| +| `OK` | All of your Postal MX hostnames are present. | +| `Missing` | None are present. Incoming mail will not reach Postal, which is fine if you only send. | +| `Invalid` | Some but not all are present. | + +### Overall status and notifications + +A domain is considered fully configured when SPF and DKIM are `OK` and both MX and Return Path are either `OK` or `Missing`. If an automatic hourly check finds a **server-level** domain in any other state, a [`DomainDNSError` webhook](/developer/webhooks#dns-error-event) is triggered. Domains with problems are also highlighted at the top of the server's pages. + +## Sending from any domain + +A server-level domain can be flagged so that the server may send from **any** `From` address once the normal checks have failed. This is intended for trusted internal systems and is shown with an **Any** label in the domain list. There is no interface for setting this flag; an administrator can set it from `postal console`: + +```ruby +org = Organization.find_by(permalink: "my-org") +org.servers.find_by(permalink: "my-server").domains.find_by(name: "yourdomain.com").update(use_for_any: true) +``` + +Messages sent this way are signed with that domain's DKIM key. From e8cbd9b43dc1d41112448b5d5732578e20cb0c35 Mon Sep 17 00:00:00 2001 From: Ben Fairless Date: Tue, 15 Sep 2026 13:42:30 +0800 Subject: [PATCH 2/5] Add users & permissions and workers & background tasks pages --- content/3.features/users-and-permissions.md | 43 +++++++++++++ .../5.other/5.workers-and-background-tasks.md | 64 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 content/3.features/users-and-permissions.md create mode 100644 content/5.other/5.workers-and-background-tasks.md diff --git a/content/3.features/users-and-permissions.md b/content/3.features/users-and-permissions.md new file mode 100644 index 0000000..aac4455 --- /dev/null +++ b/content/3.features/users-and-permissions.md @@ -0,0 +1,43 @@ +--- +title: Users & Permissions +description: 'Global administrators, organization members and what each can do.' +category: Features +--- + +Postal has a simple permission model with two kinds of user. + +## Global administrators + +Administrators have full access to every organization, server and setting in the installation. They are the only users who can: + +* See and manage **all** organizations (non-admins only see organizations they have been added to). +* Create and delete organizations. +* Manage users (**Users** in the top navigation): create users, edit their details, grant or revoke admin status and choose which organizations a non-admin user belongs to. An administrator cannot remove their own admin status or delete their own user. +* Manage [IP pools](/features/ip-pools), IP addresses and organization pool assignments. +* Change a server's **Advanced Settings** (send limit, allow sender header, privacy mode, SMTP data logging, outbound spam threshold and retention) and suspend or unsuspend servers. See [Mail server settings](/features/mail-server-settings#advanced-settings-administrators-only). +* Add domains without verifying them - domains added by an administrator are marked as verified immediately. + +The first administrator is created from the command line during installation with: + +```bash +postal make-user +``` + +This prompts for an e-mail address, first name, last name and password, and always creates an administrator. Run it again at any time to create additional administrators, for example if you have locked yourself out. + +## Organization users + +Everyone else is an ordinary user who belongs to one or more organizations. Within an organization they have full access to *all* of its mail servers and domains - there is no per-server or read-only access. This includes creating and deleting servers, managing domains, routes, endpoints, credentials, webhooks and the non-admin server settings, and viewing every message. + +Organizations record which user created them as the **owner**, but this does not currently grant any additional permissions. + +Users are added to organizations by an administrator from the **Users** page: edit the user and tick the organizations they should be able to access. Removing the last organization from a non-admin user leaves them able to log in but with nothing to see. + +## Accounts and passwords + +* Users log in with their e-mail address and password. Passwords must be at least 8 characters long. +* Password resets are available from the login page and are sent using the `smtp` section of the Postal configuration, so make sure that is set up (you can test it with `postal test-app-smtp`). +* From **My Settings** a user can change their name, e-mail address, time zone and (after confirming their current password) their password. Times throughout the interface are shown in the user's time zone, which defaults to UTC. +* After logging in you are asked whether you would like to stay logged in. Choosing **Remember me** keeps the session alive across browser restarts; otherwise it ends when the browser is closed. + +If [OpenID Connect](/features/oidc) is enabled, users can be created without a password and are linked to their identity provider account on first login. Local logins can be disabled entirely with `oidc.local_authentication_enabled: false`. diff --git a/content/5.other/5.workers-and-background-tasks.md b/content/5.other/5.workers-and-background-tasks.md new file mode 100644 index 0000000..6c0b6af --- /dev/null +++ b/content/5.other/5.workers-and-background-tasks.md @@ -0,0 +1,64 @@ +--- +title: Workers & Background Tasks +description: 'How Postal workers process the queue, coordinate with each other and run scheduled maintenance tasks.' +--- + +All mail processing in Postal happens in **worker** processes (`postal worker`). The web and SMTP servers only accept messages and place them on a queue in the main database; workers take them from the queue, deliver them, and run a set of scheduled maintenance tasks. Since Postal v3 there is no message broker - the queue lives in the `queued_messages` table and workers coordinate purely through the database. + +You can run as many worker processes as you need, on as many hosts as you need, provided they all share the same database. + +## Worker threads + +Each worker process starts a number of **work threads** (set by `worker.threads`, default `2`) plus one **tasks thread**. On start up the worker checks that the database connection pool (`main_db.pool_size`) is at least `threads + 3` and increases it automatically if not, logging a warning. + +Each work thread loops over the two jobs below. If a job found work to do, the thread immediately loops again; if neither job found anything, the thread sleeps for 5 seconds before checking again. The worker shuts down cleanly on `SIGINT`/`SIGTERM`, finishing any message it is in the middle of processing first. + +### Processing queued messages + +The `ProcessQueuedMessagesJob` atomically claims **one** message from the queue by writing a lock (`locked_by`, `locked_at`) to a row that is not locked and is ready - that is, it has no `retry_after` or its `retry_after` is at least 30 seconds in the past. Only messages with no allocated IP address, or whose allocated IP address is present on the worker's host, are considered (see [IP pools](/features/ip-pools#how-addresses-are-allocated)). + +The message is then processed: + +* **Outgoing** messages are checked against the server's suspension, send limit, credential hold, suppression list and development mode; optionally inspected for spam; parsed for click/open tracking; DKIM signed; and delivered by SMTP to the recipient's MX servers (or your configured relays). +* **Incoming** messages are matched to their bounce originals, inspected for spam, and delivered according to their [route](/features/routing-incoming-email). + +If delivery fails temporarily, the message is unlocked with a `retry_after` of `5 minutes × 1.3^attempts` and `attempts` is incremented. Once `attempts` reaches `postal.default_maximum_delivery_attempts` (default 18) the message is hard failed. The time between a message being queued and a worker picking it up is exported as the `postal_message_queue_latency` metric. + +### Batching + +When `postal.batch_queued_messages` is enabled (the default), a worker that has claimed a message also claims up to 100 further ready messages with the same **batch key** and IP address. The batch key is the destination domain for outgoing mail, or the route and endpoint for incoming mail. All messages in a batch are delivered over a single SMTP connection (or to the same HTTP endpoint), and if the connection cannot be established the whole batch is soft-failed at once rather than each message retrying the connection. Set `postal.batch_queued_messages: false` to process strictly one message at a time. + +### Delivering webhooks + +The `ProcessWebhookRequestsJob` claims one pending webhook request at a time in the same way and delivers it. See [Webhooks](/developer/webhooks#how-webhooks-are-delivered) for the retry schedule. + +### Stale locks + +If a worker crashes or is killed while holding a lock, the message stays locked. Once an hour (at :45) the `TidyQueuedMessagesTask` deletes any queued message whose lock is older than `postal.queued_message_lock_stale_days` (default 1 day). These messages are removed from the queue **without** being retried or recording a delivery, so if you see messages disappearing check for worker crashes in the logs. + +## Scheduled tasks + +Housekeeping is performed by scheduled tasks which run inside the worker. To ensure each task runs only once regardless of how many workers you have, workers hold an election for the `tasks` role using the `worker_roles` table: every 60 seconds each worker's tasks thread tries to acquire the role, which succeeds if it already holds it, if nobody holds it, or if the current holder has not renewed it for 5 minutes (i.e. it has died). Only the holder runs due tasks. The acquisition is logged (`acquired task role by creating it` / `by stealing it from a lazy worker`), and the role is released on clean shutdown. + +The next run time of each task is stored in the `scheduled_tasks` table so that schedules survive restarts. Times are in UTC. + +| Task | Schedule | What it does | +|---|---|---| +| `SendNotificationsScheduledTask` | Every minute | Sends send-limit approaching/exceeded e-mails and webhooks for servers that have recently crossed a threshold (at most once per hour each). | +| `CheckAllDNSScheduledTask` | Hourly at :15 | Re-checks SPF/DKIM/MX/return path for domains last checked over an hour ago, and CNAMEs for tracking domains. Triggers `DomainDNSError` webhooks. | +| `ExpireHeldMessagesScheduledTask` | Hourly at :15 | Cancels the hold on held messages whose hold expiry (`postal.default_maximum_hold_expiry_days`) has passed. | +| `ActionDeletionsScheduledTask` | Hourly at :15 | Permanently destroys organizations and servers that have been deleted from the interface, including dropping their message databases. | +| `CleanupAuthieSessionsScheduledTask` | Hourly at :15 | Removes expired web login sessions. | +| `PruneWebhookRequestsScheduledTask` | Hourly at :45 | Deletes webhook request history older than 10 days from each server's database. | +| `TidyQueuedMessagesTask` | Hourly at :45 | Removes queued messages with stale locks (see above). | +| `ProcessMessageRetentionScheduledTask` | Daily at 03:00 | Applies each server's [retention settings](/features/mail-server-settings#retention), dropping old raw message tables and deleting old message metadata. | +| `PruneSuppressionListsScheduledTask` | Daily at 03:00 | Removes expired entries from each server's suppression list. | + +Each task's run time is exported as the `postal_worker_task_runtime` metric. + +## Scaling + +* To handle more mail, increase `worker.threads` and/or run more worker processes (see [the postal command](/getting-started/postal-command#number-of-workers) for the standard installation). Watch `postal_message_queue_latency` to see whether messages are waiting. +* Workers on different hosts are fine, but remember the IP pool affinity rule: a message allocated to an IP address is only processed by a worker on a host that has that address. +* MariaDB is the coordination point. Each worker holds `threads + 3` connections, so size `max_connections` accordingly. +* Workers can be restarted safely with `SIGTERM` (which `docker stop` sends): they finish the message they are processing before exiting. If a worker is killed abruptly, any message it had locked stays locked and is eventually removed by the stale lock task rather than retried, so avoid `SIGKILL` where possible. From 4a985cad4d04f4f957998248a7e1f6cdf4e4fead Mon Sep 17 00:00:00 2001 From: Ben Fairless Date: Tue, 15 Sep 2026 13:54:00 +0800 Subject: [PATCH 3/5] Add page documenting the postal helper command Documents the install-repo helper script: run/setup/tool commands, flags, override files and hooks. --- content/2.getting-started/8.postal-command.md | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 content/2.getting-started/8.postal-command.md diff --git a/content/2.getting-started/8.postal-command.md b/content/2.getting-started/8.postal-command.md new file mode 100644 index 0000000..d6d483e --- /dev/null +++ b/content/2.getting-started/8.postal-command.md @@ -0,0 +1,83 @@ +--- +title: The postal command +description: 'Reference for the postal helper command installed from the postalserver/install repository.' +category: Installation +--- + +The `postal` command used throughout these docs is a small Bash script from the [installation helper repository](https://github.com/postalserver/install), which the [pre-requisites](/getting-started/prerequisites) page has you clone to `/opt/postal/install` and symlink to `/usr/bin/postal`. It wraps Docker Compose so that you don't need to remember the container commands. + +All commands operate on a `docker-compose.yml` in `/opt/postal/install`, which is generated for you from a template (`templates/docker-compose.v3.yml`) the first time you run `bootstrap`, `upgrade` or any command that needs it. Docker Compose is run with the project name `postal`, so the containers are named `postal-web-1`, `postal-smtp-1`, `postal-worker-1` and so on. + +## Running Postal + +| Command | Description | +|---|---| +| `postal start` | Start all services in the background (`docker compose up -d`). Extra arguments are passed through, e.g. `postal start web`. | +| `postal stop` | Stop and remove the containers (`docker compose down`). | +| `postal restart` | Restart all containers. Required after changing `postal.yml`. | +| `postal status` | Show the running containers (`docker compose ps`). | +| `postal logs [service]` | Show logs for all services or one of `web`, `smtp`, `worker`. Extra arguments are passed through, e.g. `postal logs -f worker`. | +| `postal bash [service]` | Open a shell inside a running service container. | +| `postal dc [args]` | Run any other `docker compose` command against the Postal project, e.g. `postal dc pull`. | + +## Setup and upgrade + +| Command | Description | +|---|---| +| `postal bootstrap hostname [path]` | Create initial configuration in `path` (default `/opt/postal/config`): `postal.yml` from the example file with your hostname and a random `rails.secret_key` filled in, a `Caddyfile`, and a 1024-bit RSA `signing.key`. Existing files are never overwritten. Also generates `docker-compose.yml` for the latest release. | +| `postal initialize` | Pull the image and run `postal initialize` inside a temporary container to create the main database and load the schema. | +| `postal upgrade [version]` | Upgrade to the given version (or the latest release). See [Upgrading](/getting-started/upgrading). | +| `postal upgrade-db` | Run database migrations only, without pulling a new image or restarting. Useful after restoring a database from another installation. | +| `postal set-version x.x.x` | Regenerate `docker-compose.yml` for a specific version without pulling or restarting anything. | + +## Other tools + +These run the corresponding command from the [container image](/other/containers#other-commands) in a temporary `runner` container. + +| Command | Description | +|---|---| +| `postal make-user` | Interactively create a global administrator user (prompts for e-mail address, first name, last name and password). | +| `postal default-dkim-record` | Print the DKIM TXT record to publish at `postal._domainkey.{return path domain}`. | +| `postal test-app-smtp address` | Send a test e-mail using the `smtp` section of your configuration. | +| `postal console` | Open a Rails console against your installation. | +| `postal version` | Print the version of Postal in the configured image. | + +## Options + +| Option | Description | +|---|---| +| `--version x.x.x` | With `bootstrap` or `upgrade`, use this version instead of looking up the latest release on GitHub. | +| `--no-git-pull` | With `upgrade`, skip updating the helper repository first. | +| `--dev` | Print the commands that would be run instead of running them. | + +Looking up the latest release requires `curl` and `jq` and makes an unauthenticated request to the GitHub API, which is rate limited. If you hit the limit, pass an explicit version. + +## Customising the installation + +### Number of workers + +To run more than one worker container, add a `docker-compose.override.yml` (see below) that sets the number of replicas for the `worker` service. This survives upgrades, whereas `postal start --scale worker=3` is reset to one worker the next time `postal upgrade` runs. + +```yaml +services: + worker: + deploy: + replicas: 3 +``` + +### Overriding the compose file + +Docker Compose automatically merges `docker-compose.override.yml` from the same directory. Use this for changes such as [log drivers](/features/logging#redirecting-logs-to-the-host-syslog), extra environment variables or resource limits, so that they survive upgrades (the generated `docker-compose.yml` is replaced on every upgrade). + +### Hooks + +If a `hooks` directory exists in `/opt/postal/install`, the Bash script with the matching name in it is run at various points. Available hook names are `pre-start`, `post-start`, `pre-stop`, `post-stop`, `pre-restart`, `post-restart`, `pre-initialize-pull`, `pre-initialize`, `post-initialize`, `pre-upgrade-pull`, `post-upgrade-pull`, `pre-upgrade-db`, `post-upgrade-db`, `post-upgrade`, `pre-bootstrap`, `post-bootstrap` and `set-postal-version`. + +## The generated compose file + +For reference, the v3 template starts the following services, all using host networking and mounting `/opt/postal/config` at `/config`: + +* `web` - `postal web-server` +* `smtp` - `postal smtp-server` with the `NET_BIND_SERVICE` capability +* `worker` - `postal worker` +* `runner` - a `tools` profile service used by the helper for one-off commands; it is not started by `postal start` From 7ae7dd255808628581c75bb60315f37f54654bc0 Mon Sep 17 00:00:00 2001 From: Ben Fairless Date: Tue, 15 Sep 2026 13:44:12 +0800 Subject: [PATCH 4/5] Link installation and DNS pages to new feature documentation --- content/2.getting-started/2.installation.md | 11 +++++++++++ content/2.getting-started/4.dns-configuration.md | 10 +++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/content/2.getting-started/2.installation.md b/content/2.getting-started/2.installation.md index 1adc920..909cf26 100644 --- a/content/2.getting-started/2.installation.md +++ b/content/2.getting-started/2.installation.md @@ -65,3 +65,14 @@ docker run -d \ Once this has started, Caddy will issue an SSL certificate for your domain and you'll be able to immediately access the Postal web interface and login with the user you created in one of the previous steps. ![Image](/screenshots/Screen-Shot-2021-07-29-23-26-18.23-Qwv2DD40v4jMEoaHtE.png) + +## Next steps + +Once you can log in: + +1. Create an organization and a mail server. +2. Add the domain you want to send from and follow the [Sending domains](/features/sending-domains) page to verify it and publish its SPF, DKIM and return path records. +3. Create an **SMTP** or **API** [credential](/features/smtp-authentication) and send a test message. +4. Point the `smtp` section of `postal.yml` at your new mail server so that Postal can send its own notification e-mails, then `postal restart`. + +The full set of commands provided by the `postal` helper is documented on [The postal command](/getting-started/postal-command) page. diff --git a/content/2.getting-started/4.dns-configuration.md b/content/2.getting-started/4.dns-configuration.md index a5ca5b1..dd8c999 100644 --- a/content/2.getting-started/4.dns-configuration.md +++ b/content/2.getting-started/4.dns-configuration.md @@ -99,7 +99,7 @@ You may wish to replace ~all with -all to make the SPF ## Return Path -The return path domain is the default domain that is used as the `MAIL FROM` for all messages sent through a mail server. You should add DNS records as below. +The return path domain is the default domain that is used as the `MAIL FROM` for all messages sent through a mail server, so bounces and auto-responses are delivered here. It is also the domain Postal signs messages with (using your `signing.key`) whenever a sending domain does not yet have a working DKIM record of its own. You should add DNS records as below. @@ -133,7 +133,7 @@ The return path domain is the default domain that is used as the `MAIL FROM` for - +
postal._domainkey.rp.postal.example.com TXTValue from postal default-dkim-recordValue from postal default-dkim-record (the postal selector is the value of dns.dkim_identifier)
@@ -141,7 +141,7 @@ The return path domain is the default domain that is used as the `MAIL FROM` for ## Route domain -If you wish to receive incoming e-mail by forwarding messages directly to routes in Postal, you'll need to configure a domain for this just to point to your server using an MX record. +If you wish to receive incoming e-mail by forwarding messages directly to routes in Postal (each route has a unique `{token}@routes.postal.example.com` address - see [Routing incoming e-mail](/features/routing-incoming-email)), you'll need to configure a domain for this just to point to your server using an MX record. @@ -186,6 +186,10 @@ If you would like to make use of Click and Open Tracking then you should set up
+## Records for each sending domain + +The records above are for the Postal installation itself. Each domain you send mail *from* additionally needs its own SPF, DKIM and (optionally) return path and MX records, which Postal generates for you and checks automatically. These are described on the [Sending domains](/features/sending-domains) page. + ## Example Postal Configuration In your `postal.yml` you should have something that looks like the below to cover the key DNS records. From 58a5d605c8c2aeb9698a3f3b96b72f0ea42ebbaf Mon Sep 17 00:00:00 2001 From: Ben Fairless Date: Tue, 15 Sep 2026 13:54:00 +0800 Subject: [PATCH 5/5] Add configuration reference page Generated from doc/config/yaml.yml at the 3.3.7 tag (111 options across 17 sections) with environment variable names derived using the GROUP_KEY convention, plus cross-references to the relevant feature pages. --- .../7.configuration-reference.md | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 content/2.getting-started/7.configuration-reference.md diff --git a/content/2.getting-started/7.configuration-reference.md b/content/2.getting-started/7.configuration-reference.md new file mode 100644 index 0000000..646e0c3 --- /dev/null +++ b/content/2.getting-started/7.configuration-reference.md @@ -0,0 +1,240 @@ +--- +title: Configuration reference +description: 'Every Postal configuration option, its default value and environment variable name.' +category: Installation +--- +This page lists every configuration option available in Postal 3.3.7. Each option can be set in the `postal.yml` configuration file under the section heading shown, or with the environment variable shown. See [Configuration](/getting-started/configuration) for how the file and environment variables are loaded. + +This list is derived from the [configuration schema](https://github.com/postalserver/postal/blob/main/lib/postal/config_schema.rb) in the Postal repository. The upstream repository also publishes it as an [example YAML file](https://github.com/postalserver/postal/blob/main/doc/config/yaml.yml) and a [list of environment variables](https://github.com/postalserver/postal/blob/main/doc/config/environment-variables.md), which may include options added after this page was written. + +An empty default means the option is unset unless you provide a value. Array options are written as YAML lists in the file and as comma-separated values in environment variables. + +## postal + +Installation-wide settings. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `web_hostname` | `POSTAL_WEB_HOSTNAME` | `postal.example.com` | The hostname that the Postal web interface runs on. | +| `web_protocol` | `POSTAL_WEB_PROTOCOL` | `https` | The HTTP protocol to use for the Postal web interface. | +| `smtp_hostname` | `POSTAL_SMTP_HOSTNAME` | `postal.example.com` | The hostname that the Postal SMTP server runs on. | +| `use_ip_pools` | `POSTAL_USE_IP_POOLS` | `false` | Should IP pools be enabled for this installation? See [IP Pools](/features/ip-pools). | +| `default_maximum_delivery_attempts` | `POSTAL_DEFAULT_MAXIMUM_DELIVERY_ATTEMPTS` | `18` | The maximum number of delivery attempts. After this many attempts a message is hard failed (and, for outgoing mail, the recipient is added to the suppression list). | +| `default_maximum_hold_expiry_days` | `POSTAL_DEFAULT_MAXIMUM_HOLD_EXPIRY_DAYS` | `7` | The number of days to hold a message before they will be expired. Held messages are released without action after this long. | +| `default_suppression_list_automatic_removal_days` | `POSTAL_DEFAULT_SUPPRESSION_LIST_AUTOMATIC_REMOVAL_DAYS` | `30` | The number of days an address will remain in a suppression list before being removed. | +| `default_spam_threshold` | `POSTAL_DEFAULT_SPAM_THRESHOLD` | `5` | The default threshold at which a message should be treated as spam. Used as the initial value for new mail servers; changeable per server. | +| `default_spam_failure_threshold` | `POSTAL_DEFAULT_SPAM_FAILURE_THRESHOLD` | `20` | The default threshold at which a message should be treated as spam failure. Used as the initial value for new mail servers; changeable per server. | +| `use_local_ns_for_domain_verification` | `POSTAL_USE_LOCAL_NS_FOR_DOMAIN_VERIFICATION` | `false` | Domain verification and checking usually checks with a domain's nameserver. Enable this to check with the server's local nameservers. | +| `use_resent_sender_header` | `POSTAL_USE_RESENT_SENDER_HEADER` | `true` | Append a `Resent-Sender` header (containing the envelope sender) to all outgoing e-mails. | +| `signing_key_path` | `POSTAL_SIGNING_KEY_PATH` | `$config-file-root/signing.key` | Path to the private key used for signing. RSA private key used to sign webhook/HTTP endpoint requests and for the return path DKIM record. | +| `smtp_relays` | `POSTAL_SMTP_RELAYS` | `[]` | An array of SMTP relays in the format of smtp://host:port. Format `smtp://host:port?ssl_mode=MODE` where MODE is `Auto`, `STARTLS`, `TLS` or `None`. When set, all outgoing mail goes via the relays. See [SMTP relays](/getting-started/configuration#smtp-relays). | +| `trusted_proxies` | `POSTAL_TRUSTED_PROXIES` | `[]` | An array of IP addresses to trust for proxying requests to Postal (in addition to localhost addresses). IP addresses or CIDR ranges. | +| `allowed_request_destinations` | `POSTAL_ALLOWED_REQUEST_DESTINATIONS` | `[]` | Hostnames or IP/CIDR ranges that outbound webhook and HTTP endpoint requests are permitted to reach even when they resolve to a private, loopback, link-local or otherwise reserved address. All other such destinations are blocked to prevent SSRF. See [Blocked destinations](/developer/http-payloads#blocked-destinations). | +| `queued_message_lock_stale_days` | `POSTAL_QUEUED_MESSAGE_LOCK_STALE_DAYS` | `1` | The number of days after which to consider a lock as stale. Messages with stale locks will be removed and not retried. See [Workers & background tasks](/other/workers-and-background-tasks#stale-locks). | +| `batch_queued_messages` | `POSTAL_BATCH_QUEUED_MESSAGES` | `true` | When enabled queued messages will be de-queued in batches based on their destination. | + +## web_server + +Settings for the `postal web-server` process. See also the `PORT` and `BIND_ADDRESS` environment variables. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `default_port` | `WEB_SERVER_DEFAULT_PORT` | `5000` | The default port the web server should listen on unless overriden by the PORT environment variable. | +| `default_bind_address` | `WEB_SERVER_DEFAULT_BIND_ADDRESS` | `127.0.0.1` | The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable. | +| `max_threads` | `WEB_SERVER_MAX_THREADS` | `5` | The maximum number of threads which can be used by the web server. | + +## worker + +Settings for `postal worker` processes. See [Workers & background tasks](/other/workers-and-background-tasks). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `default_health_server_port` | `WORKER_DEFAULT_HEALTH_SERVER_PORT` | `9090` | The default port for the worker health server to listen on. | +| `default_health_server_bind_address` | `WORKER_DEFAULT_HEALTH_SERVER_BIND_ADDRESS` | `127.0.0.1` | The default bind address for the worker health server to listen on. | +| `threads` | `WORKER_THREADS` | `2` | The number of threads to execute within each worker. The database connection pool is grown automatically to at least `threads + 3`. | + +## main_db + +The main MariaDB database which stores organizations, servers, users, domains, routes and the message queue. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `host` | `MAIN_DB_HOST` | `localhost` | Hostname for the main MariaDB server. | +| `port` | `MAIN_DB_PORT` | `3306` | The MariaDB port to connect to. | +| `username` | `MAIN_DB_USERNAME` | `postal` | The MariaDB username. | +| `password` | `MAIN_DB_PASSWORD` | | The MariaDB password. | +| `database` | `MAIN_DB_DATABASE` | `postal` | The MariaDB database name. | +| `pool_size` | `MAIN_DB_POOL_SIZE` | `5` | The maximum size of the MariaDB connection pool. Workers automatically grow this to at least `worker.threads + 3`. | +| `encoding` | `MAIN_DB_ENCODING` | `utf8mb4` | The encoding to use when connecting to the MariaDB database. | + +## message_db + +Connection details for the MariaDB server on which a separate database is created for each mail server. This may be the same server as `main_db`. Databases are named `{database_name_prefix}-server-{id}`. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `host` | `MESSAGE_DB_HOST` | `localhost` | Hostname for the MariaDB server which stores the mail server databases. | +| `port` | `MESSAGE_DB_PORT` | `3306` | The MariaDB port to connect to. | +| `username` | `MESSAGE_DB_USERNAME` | `postal` | The MariaDB username. | +| `password` | `MESSAGE_DB_PASSWORD` | | The MariaDB password. | +| `encoding` | `MESSAGE_DB_ENCODING` | `utf8mb4` | The encoding to use when connecting to the MariaDB database. | +| `database_name_prefix` | `MESSAGE_DB_DATABASE_NAME_PREFIX` | `postal` | The MariaDB prefix to add to database names. | + +## logging + +See [Logging](/features/logging). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `rails_log_enabled` | `LOGGING_RAILS_LOG_ENABLED` | `false` | Enable the default Rails logger. | +| `sentry_dsn` | `LOGGING_SENTRY_DSN` | | A DSN which should be used to report exceptions to Sentry. | +| `enabled` | `LOGGING_ENABLED` | `true` | Enable the Postal logger to log to STDOUT. | +| `highlighting_enabled` | `LOGGING_HIGHLIGHTING_ENABLED` | `false` | Enable highlighting of log lines. | + +## gelf + +Send log output to a Graylog/GELF server over UDP in addition to STDOUT. Enabled when `host` is set. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `host` | `GELF_HOST` | | GELF-capable host to send logs to. | +| `port` | `GELF_PORT` | `12201` | GELF port to send logs to. | +| `facility` | `GELF_FACILITY` | `postal` | The facility name to add to all log entries sent to GELF. | + +## smtp_server + +Settings for the `postal smtp-server` process. See [SMTP TLS](/features/smtp-tls) and [SMTP Authentication](/features/smtp-authentication). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `default_port` | `SMTP_SERVER_DEFAULT_PORT` | `25` | The default port the SMTP server should listen on unless overriden by the PORT environment variable. | +| `default_bind_address` | `SMTP_SERVER_DEFAULT_BIND_ADDRESS` | `::` | The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable. `::` listens on all IPv4 and IPv6 addresses. | +| `default_health_server_port` | `SMTP_SERVER_DEFAULT_HEALTH_SERVER_PORT` | `9091` | The default port for the SMTP server health server to listen on. | +| `default_health_server_bind_address` | `SMTP_SERVER_DEFAULT_HEALTH_SERVER_BIND_ADDRESS` | `127.0.0.1` | The default bind address for the SMTP server health server to listen on. | +| `tls_enabled` | `SMTP_SERVER_TLS_ENABLED` | `false` | Enable TLS for the SMTP server (requires certificate). | +| `tls_certificate_path` | `SMTP_SERVER_TLS_CERTIFICATE_PATH` | `$config-file-root/smtp.cert` | The path to the SMTP server's TLS certificate. | +| `tls_private_key_path` | `SMTP_SERVER_TLS_PRIVATE_KEY_PATH` | `$config-file-root/smtp.key` | The path to the SMTP server's TLS private key. | +| `tls_ciphers` | `SMTP_SERVER_TLS_CIPHERS` | | Override ciphers to use for SSL. | +| `ssl_version` | `SMTP_SERVER_SSL_VERSION` | `SSLv23` | The SSL versions which are supported. An OpenSSL version constant such as `SSLv23`, `TLSv1_2` or `TLSv1_3`. | +| `proxy_protocol` | `SMTP_SERVER_PROXY_PROTOCOL` | `false` | Enable proxy protocol for use behind some load balancers (supports proxy protocol v1 only). When enabled the `220` greeting is delayed until the `PROXY` line is received. | +| `log_connections` | `SMTP_SERVER_LOG_CONNECTIONS` | `false` | Enable connection logging. | +| `max_message_size` | `SMTP_SERVER_MAX_MESSAGE_SIZE` | `14` | The maximum message size to accept from the SMTP server (in MB). Checked once the whole message has been received; clients receive `552 Message too large`. | +| `log_ip_address_exclusion_matcher` | `SMTP_SERVER_LOG_IP_ADDRESS_EXCLUSION_MATCHER` | | A regular expression to use to exclude connections from logging. Ruby regular expression matched against the client IP address. | + +## dns + +The DNS names your installation uses. See [DNS configuration](/getting-started/dns-configuration) and [Sending domains](/features/sending-domains). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `mx_records` | `DNS_MX_RECORDS` | `mx1.postal.example.com, mx2.postal.example.com` | The names of the default MX records. | +| `spf_include` | `DNS_SPF_INCLUDE` | `spf.postal.example.com` | The location of the SPF record. | +| `return_path_domain` | `DNS_RETURN_PATH_DOMAIN` | `rp.postal.example.com` | The return path hostname. | +| `route_domain` | `DNS_ROUTE_DOMAIN` | `routes.postal.example.com` | The domain to use for hosting route-specific addresses. | +| `track_domain` | `DNS_TRACK_DOMAIN` | `track.postal.example.com` | The CNAME which tracking domains should be pointed to. | +| `helo_hostname` | `DNS_HELO_HOSTNAME` | | The hostname to use in HELO/EHLO when connecting to external SMTP servers. Falls back to `postal.smtp_hostname`. When sending from an IP pool address, the address's own hostname is used instead. | +| `dkim_identifier` | `DNS_DKIM_IDENTIFIER` | `postal` | The identifier to use for DKIM keys in DNS records. Per-domain DKIM selectors are `{dkim_identifier}-{random}`, e.g. `postal-AB1CDE._domainkey`. | +| `domain_verify_prefix` | `DNS_DOMAIN_VERIFY_PREFIX` | `postal-verification` | The prefix to add before TXT record verification string. Verification TXT records are `{domain_verify_prefix} {token}`. | +| `custom_return_path_prefix` | `DNS_CUSTOM_RETURN_PATH_PREFIX` | `psrp` | The domain to use on external domains which points to the Postal return path domain. Domains may CNAME `{prefix}.yourdomain.com` to `return_path_domain`. | +| `timeout` | `DNS_TIMEOUT` | `5` | The timeout to wait for DNS resolution. | +| `resolv_conf_path` | `DNS_RESOLV_CONF_PATH` | `/etc/resolv.conf` | The path to the resolv.conf file containing addresses for local nameservers. Used for MX lookups when sending and, when `postal.use_local_ns_for_domain_verification` is enabled, for domain checks. | + +## smtp + +The SMTP server Postal uses to send its **own** e-mails (password resets, send limit warnings, suspension notices, test messages). This is not used for mail sent through your mail servers. Once Postal is running you can point this at one of your own mail servers. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `host` | `SMTP_HOST` | `127.0.0.1` | The hostname to send application-level e-mails to. | +| `port` | `SMTP_PORT` | `25` | The port number to send application-level e-mails to. | +| `username` | `SMTP_USERNAME` | | The username to use when authentication to the SMTP server. | +| `password` | `SMTP_PASSWORD` | | The password to use when authentication to the SMTP server. | +| `authentication_type` | `SMTP_AUTHENTICATION_TYPE` | `login` | The type of authentication to use. `plain`, `login` or `cram_md5`. | +| `enable_starttls` | `SMTP_ENABLE_STARTTLS` | `false` | Use STARTTLS when connecting to the SMTP server and fail if unsupported. | +| `enable_starttls_auto` | `SMTP_ENABLE_STARTTLS_AUTO` | `true` | Detects if STARTTLS is enabled in the SMTP server and starts to use it. | +| `openssl_verify_mode` | `SMTP_OPENSSL_VERIFY_MODE` | `peer` | When using TLS, you can set how OpenSSL checks the certificate. Use 'none' for no certificate checking. `peer` or `none`. | +| `from_name` | `SMTP_FROM_NAME` | `Postal` | The name to use as the from name outgoing emails from Postal. | +| `from_address` | `SMTP_FROM_ADDRESS` | `postal@example.com` | The e-mail to use as the from address outgoing emails from Postal. | + +## rails + +Settings for the underlying Rails application. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `environment` | `RAILS_ENVIRONMENT` | `production` | The Rails environment to run the application in. Leave as `production`. | +| `secret_key` | `RAILS_SECRET_KEY` | | The secret key used to sign and encrypt cookies and session data in the application. Generated for you by `postal bootstrap`. Changing it invalidates all sessions. | + +## rspamd + +See [Spam & Virus Checking](/features/spam-and-virus-checking). If both rspamd and spamd are enabled, only rspamd is used. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `enabled` | `RSPAMD_ENABLED` | `false` | Enable rspamd for message inspection. | +| `host` | `RSPAMD_HOST` | `127.0.0.1` | The hostname of the rspamd server. | +| `port` | `RSPAMD_PORT` | `11334` | The port of the rspamd server. | +| `ssl` | `RSPAMD_SSL` | `false` | Enable SSL for the rspamd connection. | +| `password` | `RSPAMD_PASSWORD` | | The password for the rspamd server. | +| `flags` | `RSPAMD_FLAGS` | | Any flags for the rspamd server. | + +## spamd + +See [Spam & Virus Checking](/features/spam-and-virus-checking). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `enabled` | `SPAMD_ENABLED` | `false` | Enable SpamAssassin for message inspection. | +| `host` | `SPAMD_HOST` | `127.0.0.1` | The hostname for the SpamAssassin server. | +| `port` | `SPAMD_PORT` | `783` | The port of the SpamAssassin server. | + +## clamav + +See [Spam & Virus Checking](/features/spam-and-virus-checking). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `enabled` | `CLAMAV_ENABLED` | `false` | Enable ClamAV for message inspection. | +| `host` | `CLAMAV_HOST` | `127.0.0.1` | The host of the ClamAV server. | +| `port` | `CLAMAV_PORT` | `2000` | The port of the ClamAV server. | + +## smtp_client + +Timeouts (in seconds) for outgoing SMTP connections made by workers when delivering mail. + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `open_timeout` | `SMTP_CLIENT_OPEN_TIMEOUT` | `30` | The open timeout for outgoing SMTP connections. | +| `read_timeout` | `SMTP_CLIENT_READ_TIMEOUT` | `30` | The read timeout for outgoing SMTP connections. | + +## migration_waiter + +See [Our container image](/other/containers#waiting-for-database-migrations). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `enabled` | `MIGRATION_WAITER_ENABLED` | `false` | Wait for all migrations to run before starting a process. | +| `attempts` | `MIGRATION_WAITER_ATTEMPTS` | `120` | The number of attempts to try waiting for migrations to complete before start. | +| `sleep_time` | `MIGRATION_WAITER_SLEEP_TIME` | `2` | The number of seconds to wait between each migration check. | + +## oidc + +See [OpenID Connect](/features/oidc). + +| Option | Environment variable | Default | Description | +|---|---|---|---| +| `enabled` | `OIDC_ENABLED` | `false` | Enable OIDC authentication. | +| `local_authentication_enabled` | `OIDC_LOCAL_AUTHENTICATION_ENABLED` | `true` | When enabled, users with passwords will still be able to login locally. If disable, only OpenID Connect will be available. | +| `name` | `OIDC_NAME` | `OIDC Provider` | The name of the OIDC provider as shown in the UI. | +| `issuer` | `OIDC_ISSUER` | | The OIDC issuer URL. | +| `identifier` | `OIDC_IDENTIFIER` | | The client ID for OIDC. | +| `secret` | `OIDC_SECRET` | | The client secret for OIDC. | +| `scopes` | `OIDC_SCOPES` | `openid, email` | Scopes to request from the OIDC server. Must include enough scopes for the provider to return the user's e-mail address. | +| `uid_field` | `OIDC_UID_FIELD` | `sub` | The field to use to determine the user's UID. | +| `email_address_field` | `OIDC_EMAIL_ADDRESS_FIELD` | `email` | The field to use to determine the user's email address. | +| `name_field` | `OIDC_NAME_FIELD` | `name` | The field to use to determine the user's name. | +| `discovery` | `OIDC_DISCOVERY` | `true` | Enable discovery to determine endpoints from .well-known/openid-configuration from the Issuer. | +| `authorization_endpoint` | `OIDC_AUTHORIZATION_ENDPOINT` | | The authorize endpoint on the authorization server (only used when discovery is false). | +| `token_endpoint` | `OIDC_TOKEN_ENDPOINT` | | The token endpoint on the authorization server (only used when discovery is false). | +| `userinfo_endpoint` | `OIDC_USERINFO_ENDPOINT` | | The user info endpoint on the authorization server (only used when discovery is false). | +| `jwks_uri` | `OIDC_JWKS_URI` | | The JWKS endpoint on the authorization server (only used when discovery is false). |