Repository navigation
Mail Providers
ALPS reads and stores mail through a provider, chosen with [provider] type. This page covers the three providers built into ALPS (imap, maildir and multi): when to use each, how multi picks a backend for each login, and how a multi backend names its own SMTP, CardDAV, CalDAV, ManageSieve and password-change settings. Every key is listed in Config-Providers-and-SMTP and in CONFIGURATION.md.
| Provider | Use it when | Mail is read from | Each account's ALPS settings are kept in |
|---|---|---|---|
imap (the default) |
One IMAP server holds every account. | That server, over IMAP. | The server's IMAP METADATA (Storage-in-IMAP-METADATA). |
maildir |
ALPS runs on the host that holds the mail, and you do not run an IMAP server. | Maildir directories on the local disk. |
alps_store.json at the root of each user's maildir. |
multi |
Accounts of different domains live on different servers, or users sign in with a bare username and should still send from an address. | A backend chosen for each login, of type imap or maildir. |
Wherever that backend keeps them. |
These rules hold for all three:
-
[provider] typetakes"imap","maildir"or"multi", and defaults to"imap". The section for that type ([provider.imap],[provider.maildir]or[provider.multi]) is required. Without it ALPS stops at startup withmissing TOML section [provider.<type>], and any other type stops startup too. -
[smtp] serveris required whatever the provider, even when everymultibackend names its ownsmtp. - ALPS passes the login and password to the backend exactly as the user typed them. The
imapprovider signs in with them, and themaildirprovider looks the login up in its passwd file. - The provider is set up once, at startup. A change to it takes a restart.
[provider]
type = "imap"
[provider.imap]
server = "imaps://imap.example.com:993"
[smtp]
server = "smtps://smtp.example.com:465"-
serveris required and must carry a scheme:imaps://(implicit TLS, port 993 by default),imap://(STARTTLS required, port 143 by default) orimap+insecure://(no TLS). ALPS always verifies the server's certificate, so a server with a private CA needs that CA in the host's trust store. -
insecure = truemakesimap://connect without TLS, likeimap+insecure://. The password then crosses the network in clear text. -
authserv_idslists the authserv-ids of your receiving mail servers, for example["mx.example.com"]. ALPS reads a message's sender verdict, which drives the sender badge and brand logo, from theAuthentication-Resultsfield those servers wrote. Left empty, ALPS reads the topmost such field, which a message your server did not stamp may carry from its sender. Set it whenever your mail server writes that field. -
debug = truelogs the IMAP exchange, message content included, through[logging]asprotocol tracelines at debug level, so they appear only while the log level is debug.[server] debugdoes the same. Keep both off in production. -
[provider] timeout_sec(30 seconds by default) bounds connecting to the server and the wait for each of its answers,LOGINand every other command 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. For this provider only,[server] imap_timeout_secis read in its place whentimeout_secis unset.
What the server has to offer (METADATA for settings to persist, UIDPLUS and MOVE, and the optional extensions) is on Mail-Server-Requirements. For a complete local mail stack, see Setup-Dovecot-and-Postfix.
[provider]
type = "maildir"
[provider.maildir]
auth_passwd_file = "/etc/dovecot/users"
path = "/var/vmail/%d/%u/Maildir"
[smtp]
server = "smtps://smtp.example.com:465"-
auth_passwd_fileis required: a Dovecot-style passwd file. ALPS reads it, and never writes it, on every sign-in, so new users can sign in without a restart. The first field of a line is compared exactly, case included, with the login as typed. The password schemes ALPS accepts are listed in MAILDIR.md. A user whose line uses another scheme cannot sign in, and ALPS logsuser "<login>" has unsupported password scheme "<scheme>"at each attempt. -
pathis each user's own maildir once%u(the local part, or the whole login when it has no@),%d(the domain, empty when the login has no@) and%n(the whole login) are replaced. Include%n, or%utogether with%d, so that every account gets a directory of its own. Withoutpath, each user's maildir is<home>/Maildir, where<home>is field 6 of the user's passwd line. - A user can sign in before the maildir exists, and save settings too: the first save creates the maildir directory when it is missing.
- ALPS reads and writes the files as its own user and ignores the uid and gid in the passwd file, so run it as the user that owns the mail store.
- Sending still goes through
[smtp] server, which ALPS signs in to with the same login and password. The SMTP server must accept the credentials inauth_passwd_file. -
[provider] timeout_secdoes not apply to this provider.
Compared with imap, the maildir provider has no new-mail push (pages check at the user's interval instead), no storage quota, no folder sharing, no conversation threading from the server and no search across all folders. Its search reads the query as on an IMAP account, prefixes and quotes included, and ALPS does the matching itself, against the decoded headers and bodies and the flags in the file names. It shows no sender badges or brand logos, lists every folder as subscribed, and cannot store tags: a tag change is refused with HTTP 400 and changes nothing. Messages are sorted by their Date header when Settings > Reading & Composing > Message Sort Criteria is Received Date (the default), and by file name, which normally starts with the delivery time, under Folder Filing Date. Sent copies and drafts go only to folders named exactly Sent and Drafts (or Draft), in any case, which ALPS creates when they are missing. The full list is in MAILDIR.md.
Serve each maildir from one ALPS process: changes to alps_store.json are serialised only within a process. With [cluster] enabled = true, maildir accounts are not offered recovery codes for their security keys (WEBAUTHN.md).
For a walkthrough, see Setup-Maildir.
multi chooses a backend for each login when the user signs in. The session then works with that backend as if it were the only provider: an imap backend offers what its server supports, and a maildir backend has the limits listed above. The connections the session opens later, its background connection and reconnects included, go through the same choice. A linked account is routed by its own login.
ALPS first takes the domain from the login: the part after the @, lowercased. A login without @ gets default_domain when one is set. A login with more than one @ is refused, unless its local part is quoted, as in "a@b"@example.com.
It then tries these rules in order, and the first one that matches chooses the backend:
-
[provider.multi.domains."<domain>"], an entry for exactly that domain. -
[[provider.multi.routes]], the first route whosedomainslist contains the domain. -
template, when the domain is listed intemplate_domains. -
autodiscover, whenautodiscover = trueand the domain is listed inautodiscover_domains. -
[provider.multi.default], for every login no earlier rule matched, including a bare login when there is nodefault_domain.
Domain keys and route lists are lowercased when ALPS loads them, so domains = ["Example.COM"] matches a login at example.com. The backend still receives the login as typed: a maildir backend looks up jdoe@Example.COM in its passwd file as written, and fills %d with Example.COM. A bare login that default_domain routes to a maildir backend is looked up under the bare name, and its %d is empty.
What a sign-in ends in:
| Situation | HTTP answer | The sign-in page shows | Reason in the sign-in failed log line |
|---|---|---|---|
| A rule matched, and the backend refused the password | 401 | "Login failed. Please check your credentials." |
bad credentials, with the backend's answer in error
|
No rule matched, and there is no default
|
401 | "Login failed. Please check your credentials." |
bad credentials, with no mail provider configured for domain "<domain>" in error
|
The login has more than one unquoted @
|
401 | "Login failed. Please check your credentials." |
bad credentials, with malformed username in error
|
template or autodiscover matched, but no usable server was found or reached |
503, code service_unavailable
|
"The service could not sign you in just now. Please try again in a moment." |
error, with the cause in the line's error field |
Once template or autodiscover has matched a domain, a failure there ends the sign-in. ALPS does not go on to default, so a user is never signed in to a server other than the one their domain names.
The browser gets the same 401 for each of the first three, but the log tells them apart. When every user of one domain is told the password is wrong, look for no mail provider configured for domain in their sign-in failed lines, and add a rule that covers that domain.
Each domains entry, each routes entry and default describes one backend: its type ("imap", the default, or "maildir"), the keys of that provider (server and the other [provider.imap] keys, or auth_passwd_file and path), and optionally its own endpoints. A routes entry also lists its domains. [provider] timeout_sec applies to every IMAP backend, and [server] imap_timeout_sec is not read under multi. The keys, with a complete example of each kind of backend, are on Config-Providers-and-SMTP.
With that example, a user who signs in as jdoe gets default_domain = "example.com", is routed by the routes entry for example.com to mail.example.com, signs in there as jdoe, and sends from jdoe@example.com through that route's smtp. A user at a domain whose entry names no smtp sends through [smtp] server.
Both rules find an IMAP server for a domain that has no entry of its own:
-
templatebuilds the server URL from placeholders:%dfor the domain, as inimaps://mail.%d:993, and%ufor the local part.%n, the whole login, is refused at startup, since a whole login cannot form a host name. -
autodiscover = truelooks the server up in the domain's RFC 6186 SRV records,_imaps._tcpand then_imap._tcp.
Both apply only to the domains on their allowlist, template_domains and autodiscover_domains. With the list empty the rule never matches, so always set the list together with the rule. Through these rules ALPS connects only to public addresses: name a mail server on a private network in a domains or routes entry instead. Both lists accept "*", which lets the domain a user types choose the server ALPS connects to, so use it only where the sign-in page is open to users you know. The servers found this way take their endpoints from default.
The placeholders, the checks on the login, how long lookups are cached and what ALPS logs when a lookup fails are on Config-Providers-and-SMTP.
A backend can name the other services its users reach, so that a user does not read mail from one host and then send, sync contacts or calendars, edit filters or change a password somewhere unrelated. The keys are smtp, carddav, caldav, managesieve and a password sub-table, each used in place of the global setting for that backend's users.
Where a login's endpoints come from:
| The login was routed by | Each endpoint comes from |
|---|---|
a domains entry |
that entry, or else the global setting |
a routes entry |
that route, or else the global setting |
template, autodiscover or default
|
default, or else the global setting |
A domains or routes entry never borrows an endpoint from another backend. A domains entry without smtp sends through [smtp] server, even when default names an smtp.
ManageSieve signs in with the login and password the user signed in to ALPS with, and so do CalDAV and CardDAV unless the user saves a separate username and password in Settings, so those servers must accept the same credentials as the backend. With type = "multi", the caldav, carddav and managesieve plugins load without a global server when some backend names their endpoint, provided enabled lets them through (see Config-Plugins). caldav and carddav also load with a section of their own and no endpoint anywhere. A user whose backend names no CalDAV or CardDAV endpoint, with no global server either, gets only the plugin's section in Settings > Calendar or Settings > Contacts, where they can enter a server of their own; Calendar and Tasks, or Contacts, appear once they have one. A user whose backend names no ManageSieve endpoint, with no global server, sees Filters and Auto-reply, but they cannot load. For password change, with [plugin.password] enabled = true, a backend whose password block sets an endpoint or command is enough for the plugin to load without a global one.
The form each endpoint takes, the password sub-table and where it goes in TOML are on Config-Providers-and-SMTP. More on each service: MAIL_SERVER.md for SMTP, CDAV.md for contacts and calendars, FILTERS.md for filters, and Setup-Password-Change for password change.
multi also serves a single IMAP server whose users sign in with a bare username. With only default_domain and default, a user who signs in as jdoe signs in to the server as jdoe, and sends and organizes meetings as jdoe@example.com when default_domain = "example.com". With the imap or maildir provider, a bare login stays the From address. The password-change settings still receive the bare login (jdoe), as PASSWORD_CHANGE.md describes. The configuration is on Config-Providers-and-SMTP.
ALPS checks the multi section at startup. It stops in each of these cases, and where one backend is at fault, the message names its domain, the route by its index from 0, or the default backend:
- the section has no rule at all, or a
routesentry has nodomains; - a backend has an unknown
type, animapbackend has no usableserver, or amaildirbackend has noauth_passwd_file; - a key in a backend has a value of the wrong type, such as a
passwordthat is not a table; - a backend's
smtp,caldav,carddavormanagesieveis not a URL that service takes, for examplemulti: invalid service for domain "example.com": ...; -
templateorautodiscoveris set with an empty allowlist (multi: template requires non-empty template_domains), ortemplateholds%nor does not make an IMAP URL (multi: invalid template: ...).
ALPS also logs a warning for each key it does not know, such as a misspelled one (Unknown configuration key: ... (ignored)), but not for the keys inside a backend's password table, which are free-form.
After changing the multi section, sign in as one user of each backend, send a message, and open Contacts, Calendar and Filters. Then search the log for sign-in failed.
- Setup-Local-Try-Out: ALPS on your own machine.
-
Setup-Dovecot-and-Postfix: the
imapprovider on a self-hosted mail server. -
Setup-Maildir: the
maildirprovider, without an IMAP server. -
Setup-Many-Domains: the
multiprovider with several domains and their own endpoints. - Setup-Password-Change: password change, including one block per backend.
- Running-in-Production: what to check before going live.
A provider registers itself from init() with provider.Register(name, factory), and [provider] type selects it by that name. Handlers reach mail only through the provider.MailProvider interface; optional interfaces switch on features such as push, quota and folder sharing. The API's JSON reuses the IMAP library's types, so a new backend has to return data that maps onto them. See ARCHITECTURE.md and the code: provider/imap/config.go, provider/maildir/config.go, provider/multi/config.go (routing and per-backend endpoints) and provider/multi/autodiscover.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