Repository navigation
Config Providers and SMTP
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.
[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] typepicks the provider, and the section of the same name ([provider.imap],[provider.maildir]or[provider.multi]) is required. -
[smtp] serveris required whatever the provider. - ALPS reads these sections once, at startup. A change takes a restart;
SIGHUPonly 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
smptin adomainsentry, for example, givesUnknown configuration key: provider.multi.domains."example.org".smpt (ignored). The keys of a[provider.<type>]section for a type other than the one[provider] typeselects are reported the same way. The keys inside a backend'spasswordtable are not checked. Compare key names against this page or config.example.toml.
| 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.
| 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_FILEandSSL_CERT_DIRname 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_idswhenever your receiving server writesAuthentication-Results. Keepdebugoff in production: at level debug, it writes message content and other private traffic to the log. - Plaintext (
imap+insecure://, orinsecure = 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
|
| 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%utogether with%d, inpath, so that every account gets a directory of its own. With no placeholder every account shares one maildir, and with%ualonejohn@example.comandjohn@example.orgshare one. - ALPS opens
auth_passwd_fileat 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.comsigned in andJANE@example.comgot 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 sayshas 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.
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:
- a
domainsentry for exactly that domain; - the first
routesentry whosedomainslist contains it; -
template, if the domain is intemplate_domains; -
autodiscover, if it is on and the domain is inautodiscover_domains; -
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.
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"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"]-
templatereplaces%dwith the domain and%uwith the local part, and must produce animaps://,imap://orimap+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 withmulti: 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. -
autodiscoverlooks 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'smtpand other endpoints come fromdefault, or from the global settings. - The DNS lookups of both rules are capped at 5 seconds, or at
[provider] timeout_secwhen 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, CGNAT100.64.0.0/10,192.0.0.0/24,198.18.0.0/15and240.0.0.0/4, broadcast included), is refused. The address is checked again when ALPS connects. In the lab, a template that pointed at127.0.0.1got 503 withtarget address 127.0.0.1 is restrictedin the log. Name a server on a private network in adomainsorroutesentry instead. See The Address Check. - The servers these rules find have no
insecure,debugorauthserv_idsof their own; only[server] debugapplies to them. Give a domain an entry of its own when it needsauthserv_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.
templatewith an emptytemplate_domains, orautodiscover = truewith an emptyautodiscover_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.
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.
-
smtptakes the schemes of[smtp] server.[smtp] insecureapplies to it too: withinsecure = true, a backend'ssmtp://URL sends in plaintext. -
carddav,caldavandmanagesievetake the schemes of the plugins' ownserverkeys. Acarddavorcaldavvalue needs a scheme, and there is no DNS discovery for a per-backendcaldav.managesievetakes the same forms as[plugin.managesieve] server, but write it with a scheme too, such asmanagesieve://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", thecaldav,carddavandmanagesieveplugins load without a globalserverwhen at least one backend names their endpoint, providedenabledlets them through (see Config-Plugins). A user whose backend names no endpoint for one of them, with no globalservereither, finds filters not configured, and gets Calendar or Contacts only after setting up a server of their own in Settings (Config-Plugins). -
[smtp] serverstays required, even when every backend names its ownsmtp.
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 = trueSetting 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.
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
maildirbackend, 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
maildirbackend that accepts it, the lab'sjdoesigned in with the addressjdoe@example.com. With theimapormaildirprovider, withoutmulti, the same login keepsjdoeas its address. - For a bare login,
%din amaildirbackend'spathis empty.
| 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, andsmtp+insecure://127.0.0.1at127.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 withAUTH PLAINusing 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 withSMTP operation failed after 3 attempts. -
[server] smtp_timeout_sec(default 30) bounds opening the connection: the TCP connect, with the TLS handshake forsmtps://. Forsmtp://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 = trueturns everysmtp://URL, the backends' included, into plaintext, so prefersmtp+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
|
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.
-
Mail-Providers: how each provider behaves and how
multiroutes a login. - Mail-Server-Requirements: what the IMAP and SMTP servers must support.
- Setup-Many-Domains, Setup-Maildir, Setup-Dovecot-and-Postfix and Setup-Password-Change: complete setups.
- Configuration-Overview, Config-Server and Config-Plugins: the other sections of the file.
- Code: cmd/alps/config.go, provider/imap/config.go, provider/maildir/config.go, provider/multi/config.go, provider/multi/autodiscover.go, smtp.go and server.go.
These pages describe ALPS as it is on the main branch; the reference documents live in the repository's docs/ directory.
Architecture
- Architecture Overview
- Mail Server Requirements
- Sessions and Sign-in
- Storage in IMAP METADATA
- Mail Providers
- Plugins
- Features
- Security Features
- Clustering
Tests
Configuration
- Configuration Overview
- Server
- Providers and SMTP
- TLS
- Cluster
- Rate Limiting and WebAuthn
- Logging and Cache
- Plugins
- Branding
Common Setups