Skip to content

Config Providers and SMTP

Dejan Strbac edited this page Nov 12, 2026 · 1 revision

This page lists the keys that tell ALPS where mail is read from and where it is sent: [provider], [provider.imap], [provider.maildir], [provider.multi] with its backends and per-backend endpoints, and [smtp]. Each section gives the keys with their defaults, an example, and what to check. How the three providers behave, and how multi picks a backend for a login, is explained on Mail-Providers. The full reference is section 8 of CONFIGURATION.md.

The smallest working configuration

[provider]
type = "imap"

[provider.imap]
server = "imaps://imap.example.com:993"

[smtp]
server = "smtps://smtp.example.com:465"

These rules apply to every configuration:

  • [provider] type picks the provider, and the section of the same name ([provider.imap], [provider.maildir] or [provider.multi]) is required.
  • [smtp] server is required whatever the provider.
  • ALPS reads these sections once, at startup. A change takes a restart; SIGHUP only reopens the log file.
  • ALPS ignores keys it does not recognize, and behaves as if they were absent, but logs a warning for each one at startup. A misspelled smpt in a domains entry, for example, gives Unknown configuration key: provider.multi.domains."example.org".smpt (ignored). The keys of a [provider.<type>] section for a type other than the one [provider] type selects are reported the same way. The keys inside a backend's password table are not checked. Compare key names against this page or config.example.toml.

[provider]

Key Type Default What it does
type string "imap" "imap", "maildir" or "multi", in any case. Any other value stops startup, with missing TOML section [provider.<type>], or with unknown provider type when such a section exists.
timeout_sec integer 30 Bounds connecting to an IMAP server (the TCP connect, plus the TLS handshake for imaps://) and the wait for each of the server's answers: the greeting, LOGIN and every later command, IDLE and DONE included. A fetched message, or a part of one, has a fixed 5 minutes to arrive instead. An answer that does not come in time closes the connection. With multi it applies to every backend. maildir ignores it.

For the imap provider only, [server] imap_timeout_sec is read in place of timeout_sec when timeout_sec is unset or 0. It is ignored for multi and maildir, so set [provider] timeout_sec there. In the test lab, timeout_sec = 3 and, separately, imap_timeout_sec = 2 with no timeout_sec, both against an address that never answers, ended the sign-in after 3 and 2 seconds with a 503 and i/o timeout in the log. The HTTP and SMTP timeouts are on Config-Server.

[provider.imap]

Key Type Default What it does
server string none, required The IMAP server, as a URL with one of the schemes below. A missing scheme, an unknown scheme or an empty value stops startup.
insecure boolean false Makes imap:// connect in plaintext, without STARTTLS, like imap+insecure://. It has no effect on imaps://.
debug boolean false Logs the IMAP exchange through [logging], as protocol trace lines at debug level, and has the provider log notes at debug level, for example when SORT or THREAD fails and ALPS falls back. Both appear only with [logging] level = "debug". [server] debug does both.
authserv_ids list of strings [] The authserv-ids of your receiving mail servers, for example ["mx.example.com"], matched in any case. ALPS reads a message's sender verdict, which drives the sender badge, the brand logo and the warning above a failed message, from the topmost Authentication-Results field one of them wrote. Left empty, ALPS reads the topmost field, whoever wrote it.
Scheme Transport Default port
imaps:// Implicit TLS 993
imap:// STARTTLS. The sign-in fails if the server does not offer it. 143
imap+insecure:// Plaintext, no TLS 143
[provider.imap]
server = "imaps://imap.example.com:993"
authserv_ids = ["mx.example.com"]
  • ALPS always verifies the server's certificate, even with insecure = true. A server with a private CA needs that CA in the host's trust store; on Linux and the BSDs, SSL_CERT_FILE and SSL_CERT_DIR name it (environment variables).
  • ALPS signs in with IMAP LOGIN, sending the login and password exactly as the user typed them. What the server has to support is on Mail-Server-Requirements.
  • Set authserv_ids whenever your receiving server writes Authentication-Results. Keep debug off in production: at level debug, it writes message content and other private traffic to the log.
  • Plaintext (imap+insecure://, or insecure = true) sends the user's password unencrypted. Use it only to a server on the same host or a network you control.

Results from the test lab, against an IMAP server without TLS:

server Sign-in
imap+insecure://127.0.0.1:8961 200
imap://127.0.0.1:8961 503, log: failed to connect to IMAP server: imap: NO STARTTLS not supported
imap://127.0.0.1:8961 with insecure = true 200
imaps://127.0.0.1:8961 with insecure = true 503, log: tls: first record does not look like a TLS handshake
imap+insecure://127.0.0.1 (no port) 503, log: dial tcp 127.0.0.1:143
imaps://127.0.0.1 (no port) 503, log: dial tcp 127.0.0.1:993

[provider.maildir]

Key Type Default What it does
auth_passwd_file string none, required A Dovecot-style passwd file. ALPS checks each sign-in against it. Without it, startup stops.
path string <home>/Maildir A pattern for each user's own maildir, the directory that holds cur, new, tmp, the .Folder directories and alps_store.json. Without it, <home> is field 6 of the user's line in auth_passwd_file.

The placeholders in path:

Placeholder For john@example.com For john
%u john john
%d example.com empty
%n john@example.com john
[provider]
type = "maildir"

[provider.maildir]
auth_passwd_file = "/etc/dovecot/users"
path = "/var/vmail/%d/%u/Maildir"

[smtp]
server = "smtps://smtp.example.com:465"
  • Put %n, or %u together with %d, in path, so that every account gets a directory of its own. With no placeholder every account shares one maildir, and with %u alone john@example.com and john@example.org share one.
  • ALPS opens auth_passwd_file at every sign-in, so a line you add works without a restart. The login is compared with the first field exactly, case included: in the lab, jane@example.com signed in and JANE@example.com got 401.
  • Without path, every line needs a home directory in field 6. A user whose line has none got 503 at sign-in in the lab, and the log says has no home directory in passwd file and no path pattern is configured.
  • Sending still goes through [smtp] server, signed in with the same login and password, so the SMTP server must accept the credentials in the passwd file.

The file format, the password schemes ALPS accepts and file permissions are in MAILDIR.md. For a walkthrough, see Setup-Maildir.

[provider.multi]

multi picks a backend for each login when the user signs in, from the domain of the login. The session then works with that backend alone. These keys go directly under [provider.multi]:

Key Type Default What it does
default_domain string none The domain given to a login without @, for routing and for the user's address. See Bare usernames.
domains tables none [provider.multi.domains."<domain>"]: one backend for exactly that domain.
routes list of tables none [[provider.multi.routes]]: one backend for every domain in its domains list.
template string none An IMAP server URL with placeholders, built for each login whose domain is in template_domains.
template_domains list of strings [] The domains template applies to. An empty list matches no domain.
autodiscover boolean false Looks up the IMAP server in the domain's RFC 6186 SRV records, for domains in autodiscover_domains.
autodiscover_domains list of strings [] The domains autodiscover applies to. An empty list matches no domain.
default table none [provider.multi.default]: the backend for every login that no rule above matched.

ALPS takes the domain after the @, lowercased, and tries the rules in this order. The first one that matches decides:

  1. a domains entry for exactly that domain;
  2. the first routes entry whose domains list contains it;
  3. template, if the domain is in template_domains;
  4. autodiscover, if it is on and the domain is in autodiscover_domains;
  5. default.

Domain keys, route lists and the two allowlists are lowercased when ALPS loads them: in the lab, domains = ["Example.TEST"] routed a login at example.test. Once template or autodiscover has matched a domain, a failure there ends the sign-in, and ALPS does not go on to default. A login that no rule matches, with no default, is refused like a wrong password (401). More on routing: Mail-Providers.

Backend keys

Each domains entry, each routes entry and default describes one backend and takes these keys:

Key In What it does
type every backend "imap" (the default) or "maildir".
server, insecure, debug, authserv_ids imap backends As in [provider.imap]. server is required.
auth_passwd_file, path maildir backends As in [provider.maildir]. auth_passwd_file is required.
domains routes entries The list of domains this route serves. Required.
smtp every backend The submission server for this backend's users, in place of [smtp] server.
carddav, caldav, managesieve every backend The contacts, calendar and filter servers for this backend's users, in place of the server of [plugin.carddav], [plugin.caldav] and [plugin.managesieve]. Write each as a full URL with a scheme.
password every backend A sub-table with the keys of [plugin.password.options], used in its place for this backend's users. It must be a table.

ALPS checks every backend when it starts. A key with a value of the wrong type, a password that is not a table, and an smtp, carddav, caldav or managesieve value that the service could not use all stop startup, with a message that names the backend (see Checking a configuration).

[provider]
type = "multi"

[provider.multi]
default_domain = "example.com"

# One domain on its own server, with its own submission server
[provider.multi.domains."example.org"]
server = "imaps://imap.example.org:993"
smtp = "smtps://smtp.example.org:465"

# One domain in local maildirs; it sends through [smtp] server
[provider.multi.domains."local.example.net"]
type = "maildir"
auth_passwd_file = "/etc/dovecot/users"
path = "/var/vmail/%d/%u/Maildir"

# Two domains sharing one server and its services
[[provider.multi.routes]]
domains = ["example.com", "example.info"]
server = "imaps://mail.example.com:993"
smtp = "smtps://mail.example.com:465"
carddav = "https://dav.example.com"
caldav = "https://dav.example.com"
managesieve = "managesieve://mail.example.com:4190"

# Every other domain
[provider.multi.default]
server = "imaps://imap.example.net:993"
smtp = "smtps://smtp.example.net:465"

[smtp]
server = "smtps://smtp.example.net:465"

Template and autodiscovery

Both rules find an IMAP server for a domain that has no entry of its own. Both apply only to the domains on their list.

[provider.multi]
template = "imaps://mail.%d:993"
template_domains = ["example.com", "example.org"]

autodiscover = true
autodiscover_domains = ["example.net"]
  • template replaces %d with the domain and %u with the local part, and must produce an imaps://, imap:// or imap+insecure:// URL. ALPS checks this at startup by filling the template with sample values: a template that does not produce an IMAP URL stops startup with multi: invalid template: .... %n, the whole login, is refused at startup (multi: template does not support %n (full login); use %u and %d), because the @ in a login cannot form a host name.
  • For template, the domain must be a host name of lowercase letters, digits, dots and inner hyphens. When the template holds %u, the local part must be 1 to 128 ASCII characters without spaces, control characters or any of : / ? # @ [ ] \ %. A login that fails these checks gets 401, like a wrong password.
  • autodiscover looks up _imaps._tcp.<domain> (implicit TLS) first, then _imap._tcp.<domain> (STARTTLS), and uses the first usable target. Each ALPS process keeps a server it found for 15 minutes, and a failed lookup for 30 seconds, during which sign-ins for that domain fail. It finds only the IMAP server: the users' smtp and other endpoints come from default, or from the global settings.
  • The DNS lookups of both rules are capped at 5 seconds, or at [provider] timeout_sec when that is lower.
  • These two rules connect only to public addresses. A server whose name resolves to a loopback, private, link-local, multicast or unspecified address, or to other reserved space (0.0.0.0/8, CGNAT 100.64.0.0/10, 192.0.0.0/24, 198.18.0.0/15 and 240.0.0.0/4, broadcast included), is refused. The address is checked again when ALPS connects. In the lab, a template that pointed at 127.0.0.1 got 503 with target address 127.0.0.1 is restricted in the log. Name a server on a private network in a domains or routes entry instead. See The Address Check.
  • The servers these rules find have no insecure, debug or authserv_ids of their own; only [server] debug applies to them. Give a domain an entry of its own when it needs authserv_ids.
  • Both lists accept "*", which matches every domain. The domain a user types then chooses the server ALPS connects to, so use "*" only on a deployment that no one but users you know can reach.
  • Each rule needs its list. template with an empty template_domains, or autodiscover = true with an empty autodiscover_domains, stops startup (multi: template requires non-empty template_domains, multi: autodiscover requires non-empty autodiscover_domains). After a change, sign in once for a listed domain, since the startup check cannot tell whether the servers exist.

Per-backend endpoints

A backend can name the other servers its users reach, so that a user does not read mail on one host and then send, sync contacts or calendars, edit filters or change a password on an unrelated one. Where each endpoint of a login comes from:

The login was routed by Each endpoint comes from
a domains entry that entry, else the global setting
a routes entry that route, else the global setting
template, autodiscover or default default, else the global setting

A domains or routes entry never takes an endpoint from another entry or from default. In the lab, with a domains entry that named no smtp, a default that named one and a global [smtp] server that could not be reached, sending from that domain failed: the entry used the global server, not the default's.

  • smtp takes the schemes of [smtp] server. [smtp] insecure applies to it too: with insecure = true, a backend's smtp:// URL sends in plaintext.
  • carddav, caldav and managesieve take the schemes of the plugins' own server keys. A carddav or caldav value needs a scheme, and there is no DNS discovery for a per-backend caldav. managesieve takes the same forms as [plugin.managesieve] server, but write it with a scheme too, such as managesieve://mail.example.com:4190. Contacts, calendars and filters sign in with the user's own login and password by default, so those servers must accept the same credentials as the backend. See CDAV.md and FILTERS.md.
  • ALPS checks each of these values at startup, with the same rules it uses when it connects, and a value it cannot use stops startup. It does not contact the servers then.
  • With type = "multi", the caldav, carddav and managesieve plugins load without a global server when at least one backend names their endpoint, provided enabled lets them through (see Config-Plugins). A user whose backend names no endpoint for one of them, with no global server either, finds filters not configured, and gets Calendar or Contacts only after setting up a server of their own in Settings (Config-Plugins).
  • [smtp] server stays required, even when every backend names its own smtp.

The password sub-table replaces [plugin.password.options] as a whole. Nothing is merged from the global block, payload_mapping included, and [plugin.password] enabled = true is still required. A backend's block that sets endpoint or command is enough to load the password plugin, with no global endpoint or command. In TOML, [provider.multi.routes.password] belongs to the route declared just above it, so put each one directly under its own [[provider.multi.routes]]. A domains entry takes [provider.multi.domains."<domain>".password], and default takes [provider.multi.default.password].

[[provider.multi.routes]]
domains = ["example.com", "example.info"]
server = "imaps://mail.example.com:993"

# Belongs to the route above
[provider.multi.routes.password]
endpoint = "https://admin.example.com/api/mailboxes/{email}"
method = "PUT"
auth_type = "bearer"
token = "..."
payload = "json"

[provider.multi.routes.password.payload_mapping]
password = "new_password"

[plugin.password]
enabled = true

# Once any [plugin.*] section sets enabled = true, only base and those plugins load
[plugin.gpg]
enabled = true

Setting enabled = true in [plugin.password] turns on the enabled list, so every other plugin you use, such as gpg, caldav or carddav, must set it too, or ALPS starts without it. base, with sign-in and mail, always loads (Config-Plugins).

In the lab, a route block without payload_mapping sent its PUT with the body {}, while a domain without a block of its own sent the global request, with the global payload_mapping. Give each block its own payload_mapping, or a command. The keys are in PASSWORD_CHANGE.md, and a walkthrough is on Setup-Password-Change.

Bare usernames

With default_domain, a login without @ is routed as if it had that domain, and <login>@<default_domain> is the user's address: messages are sent from it and meetings are organized from it.

[provider]
type = "multi"

[provider.multi]
default_domain = "example.com"

[provider.multi.default]
server = "imaps://imap.example.com:993"

[smtp]
server = "smtps://smtp.example.com:465"
  • The backend still receives the login as typed. The IMAP server, or the passwd file of a maildir backend, must accept the bare name. In the lab, a bare login routed to an IMAP server that knows its accounts only by full address got 401.
  • With a maildir backend that accepts it, the lab's jdoe signed in with the address jdoe@example.com. With the imap or maildir provider, without multi, the same login keeps jdoe as its address.
  • For a bare login, %d in a maildir backend's path is empty.

[smtp]

Key Type Default What it does
server string none, required The submission server, as a URL with one of the schemes below. Without it, startup stops with no SMTP server specified in config file ([smtp] server).
insecure boolean false Makes smtp:// send in plaintext, without STARTTLS, like smtp+insecure://. It also applies to the smtp URL of every multi backend. It has no effect on smtps://.
Scheme Transport Default port
smtps:// Implicit TLS 465
smtp:// STARTTLS. The send fails if the server does not offer it. 587
smtp+insecure:// Plaintext, no TLS 587
[smtp]
server = "smtps://smtp.example.com:465"
  • A host without a port gets the default port, a bracketed IPv6 address included: in the lab, smtps://[::1] was dialled at [::1]:465, and smtp+insecure://127.0.0.1 at 127.0.0.1:587.
  • ALPS always verifies the server's certificate, even with insecure = true.
  • ALPS opens a new connection for each message, says EHLO localhost, and authenticates with AUTH PLAIN using the login and password the user signed in with. The submission server must accept those credentials, and must let the login send from each of the user's identities. See MAIL_SERVER.md.
  • A send is tried up to 3 times in all, on a fresh connection each time, with pauses of 1 and then 2 seconds, after a failed connection or a temporary (4xx) reply. These are not tried again: any error reply to AUTH (4xx included), a permanent (5xx) reply, and a connection lost after the message was written. In the lab, a server that refused connections failed the send after about 3 seconds with SMTP operation failed after 3 attempts.
  • [server] smtp_timeout_sec (default 30) bounds opening the connection: the TCP connect, with the TLS handshake for smtps://. For smtp:// it then bounds the greeting, the first EHLO and STARTTLS together. After that, for every scheme, it bounds each SMTP command and the wait for the server's answer after the message data. A connection whose setup runs out of time is closed. See Config-Server.
  • Plaintext sends the user's password unencrypted. insecure = true turns every smtp:// URL, the backends' included, into plaintext, so prefer smtp+insecure:// on the one server that needs it, such as a relay on the same host.
  • ALPS also reads the message size limit the submission server advertises (SMTP SIZE). It connects to the user's submission server without signing in, says EHLO with a timeout of at most one second, and keeps the answer for 10 minutes in each session. The limit can lower the attachment limit the composer gets (see Config-Server).

Results from the test lab, against a submission server without TLS:

[smtp] Send
server = "smtp+insecure://127.0.0.1:8965" 200
server = "smtp://127.0.0.1:8965" 500, smtp: server doesn't support STARTTLS
server = "smtp://127.0.0.1:8965", insecure = true 200
server = "smtps://127.0.0.1:8965" 500, tls: first record does not look like a TLS handshake

Checking a configuration

ALPS stops at startup when it finds one of these mistakes. Most are found while the file is read, before the log is opened, and are written to stderr after Error loading config file: or Error generating options:. The SMTP server's URL is checked a step later, so its messages go to the log.

Mistake Message
No [smtp] server no SMTP server specified in config file ([smtp] server)
[smtp] server = "smtp.example.com", or "127.0.0.1:8965" (no scheme) SMTP server requires a scheme (smtps://, smtp://, smtp+insecure://), got: smtp.example.com (in the log)
An unknown SMTP scheme unknown scheme for SMTP server: submission (in the log)
No section for the chosen type provider.multi: missing TOML section [provider.multi]
An unknown provider type unknown provider type 'pop3'
An IMAP server that is missing or has no scheme IMAP server requires a scheme (imaps://, imap://, imap+insecure://), got: imap.example.com:993, or got empty string when it is missing
An unknown IMAP scheme unknown scheme for IMAP server: pop3s
A maildir provider without auth_passwd_file no password file specified in config file for maildir provider ([provider.maildir] auth_passwd_file)
A multi section with none of domains, routes, template, autodiscover and default multi: no domains, routes, template, autodiscover, or default provider configured
A routes entry without domains multi: route at index 0 has no domains
A backend with a bad type, server or auth_passwd_file the message above, after a prefix that names the backend: multi: failed to load provider config for domain "example.test":, multi: route [0] failed to load provider:, multi: failed to load default provider config:, or the same with initialize provider options
A value of the wrong type, such as smtp = 5, or a password that is not a table multi: invalid domains entry "example.test": ..., multi: invalid route at index 0: ... or multi: invalid default provider entry: ..., followed by the cause, such as ... incompatible types or password must be a table
A backend's smtp, carddav, caldav or managesieve value that the service cannot use multi: invalid service for domain "example.test": smtp: SMTP server requires a scheme ..., multi: invalid service for route [0]: ... or multi: invalid service for default provider: ...
template with an empty template_domains multi: template requires non-empty template_domains
autodiscover = true with an empty autodiscover_domains multi: autodiscover requires non-empty autodiscover_domains
A template that holds %n multi: template does not support %n (full login); use %u and %d
A template that does not produce an IMAP URL multi: invalid template: ..., followed by the IMAP message above

A misspelled key does not stop startup. ALPS ignores it and logs Unknown configuration key: ... (ignored) at warn level, so read the startup warnings after every change.

What a failed sign-in shows in the log, on its sign-in failed line:

Answer reason and error Usual cause
401 reason="bad credentials", with the cause in error A wrong password, where error carries the mail server's answer. A login refused by the template checks (invalid domain ... for template routing, invalid username local-part ...). A multi login that no rule matches: no mail provider configured for domain "example.org". When every user of one domain gets 401, look at error and check that a rule covers that domain. The browser gets only the 401.
503 reason=error, error="failed to connect to IMAP server: ..." (or IMAPS server) The server cannot be reached, or the scheme does not match the server (STARTTLS or TLS).
503 reason=error, error naming LOGINDISABLED or [PRIVACYREQUIRED] The server refuses a password on this connection and wants a protected one. With LOGINDISABLED, ALPS does not send the password at all. Check the scheme of server.
503 reason=error, error="access to restricted server address ... is forbidden: target address ... is restricted ...", or ... target host ... resolves to restricted address ... template produced a server on a non-public address.
503 reason=error, error="cannot resolve server address ...: lookup ...: no such host" template produced a host name that does not resolve.
503 reason=error, error="autodiscovery failed for domain ..." No usable SRV record for the domain.

After changing these sections, restart ALPS, sign in as one user of each backend, send a message, and open Contacts, Calendar and Filters. Then search the log for Unknown configuration key and sign-in failed. Before going live, also go through Running-in-Production.

Related pages

Clone this wiki locally