Repository navigation
Mail Server Requirements
ALPS keeps no mail of its own. It reads and files mail on an IMAP server, keeps each account's records on that server as IMAP METADATA, and sends through an SMTP submission server. This page says what those two servers must provide, what ALPS uses when a server offers it and what changes without it, which folders ALPS files into, what the CalDAV, CardDAV and ManageSieve servers need, and how to check a server before you point ALPS at it. The full reference is docs/MAIL_SERVER.md.
The IMAP parts apply to the imap provider and to multi backends of type imap. The maildir provider needs no IMAP server (see Setup-Maildir), but it still sends through SMTP.
| What | Needed | Without it |
|---|---|---|
| IMAP4rev1 (RFC 3501) or IMAP4rev2 (RFC 9051) | Required | ALPS cannot work |
LOGIN accepted on the connection ALPS opens |
Required | Nobody can sign in |
METADATA or METADATA-SERVER (RFC 5464) |
Required for anything to be kept | Settings, identities, linked accounts, security keys, the OpenPGP keyring and saved CalDAV and CardDAV connections last for one IMAP connection only |
UIDPLUS (RFC 4315) |
Strongly recommended | A permanent delete ends with a plain EXPUNGE
|
MOVE (RFC 6851) |
Recommended | A move is a COPY followed by a delete |
| A Sent folder and a Drafts folder | Required | A sent message gets no copy in Sent, and drafts cannot be saved |
An SMTP submission server that takes AUTH PLAIN with the sign-in credentials |
Required | ALPS does not start without [smtp] server, and sends fail without AUTH PLAIN |
Everything else is optional: ALPS uses it when the server lists it, and three capabilities only when you also turn them on. The sections below go through each item.
[provider.imap] server names the server with one of three schemes. The keys are on Config-Providers-and-SMTP.
| Scheme | Transport | Default port |
|---|---|---|
imaps:// |
Implicit TLS | 993 |
imap:// |
STARTTLS, always. The connection fails if the server does not upgrade it. | 143 |
imap+insecure:// |
Plaintext | 143 |
ALPS checks the server's certificate against the host's system roots. insecure = true under [provider.imap] turns imap:// into plaintext; it does not relax certificate checks and never changes imaps://. Over plaintext, users' passwords cross the network unencrypted, so keep it to a lab.
[provider] timeout_sec (default 30 seconds) bounds the connect, with the TLS handshake for imaps://, and the wait for each of the server's answers: the greeting, LOGIN and every later command. 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, so a LOGIN that goes unanswered gets the 503 of a lost connection (below).
ALPS signs in with the IMAP LOGIN command, sending the username and password exactly as the user typed them on the sign-in page. It does not use AUTHENTICATE (SASL) or OAuth. The server must therefore accept LOGIN on the connection ALPS opens, which for imap:// is after STARTTLS. The server's answer decides what the user sees:
Answer to LOGIN
|
The sign-in gets |
|---|---|
OK |
The session, or the request for a second factor |
NO with no response code, or a code not listed below |
401 Failed to login: wrong username or password |
NO [UNAVAILABLE], NO [SERVERBUG], NO [PRIVACYREQUIRED], BAD, or a lost or refused connection |
503 service_unavailable
|
NO [LIMIT] |
503 connection_limit: the account has as many connections open as the server allows |
If the server lists LOGINDISABLED on the connection ALPS opens, ALPS does not send LOGIN at all: it closes the connection, and the sign-in gets the same 503 service_unavailable.
Only the 401 case counts as a failed attempt in ALPS's own sign-in limits (see docs/RATELIMIT.md). ALPS's sign-in failed log line for it carries the server's answer in its error field; the browser gets only the 401.
A server that refuses LOGIN on a plaintext connection answers NO [PRIVACYREQUIRED] (Dovecot 2.4 does, with its default auth_allow_cleartext = no), or lists LOGINDISABLED before the connection is protected. Both ask for a protected connection, so ALPS treats them as a service problem, not as a wrong password: the attempt does not count against the account, and the sign-in page says that the service could not sign the user in just now. If every sign-in gets that answer, check that server uses imaps:// or imap://, not imap+insecure://, and that insecure is not set.
Each signed-in browser is a session, and each session holds its own IMAP connection. By default it opens a second one for background work when it first needs it, and closes that one after a minute unused. With push_new_mail on, the second connection stays open while a mail page is open. A session can hold a third for a moment while a command it gave up on is still running. Every one of these comes from ALPS's address.
So the server's per-user and per-address connection limits must leave room for about two connections per browser a user signs in from, plus the user's other mail apps. Dovecot's mail_max_userip_connections, for one, counts every connection of one user from one address, and is 10 by default. Sessions-and-Sign-in explains the session model, and background_connection and push_new_mail are on Config-Server.
ALPS has no database. It keeps each account's settings (identities included), linked accounts, two-factor sign-in data, OpenPGP keyring and saved CalDAV and CardDAV connections on the IMAP server, as private server METADATA entries on mailbox "" under /private/vendor/alps/. It uses this store when the server lists METADATA or METADATA-SERVER. Storage-in-IMAP-METADATA describes the store.
ALPS does not check for METADATA at startup. On a server without it, ALPS starts and signs users in, but:
- each IMAP connection gets a store of its own, in memory. A setting saved now is gone after the next sign-in or reconnect, and the session's background connection does not see it.
- ALPS logs once per process:
alps/provider: IMAP server doesn't support the METADATA extension, using transient store instead. - two-factor sign-in with security keys cannot be used, since a key that is added is not kept. In a cluster, recovery codes also need the METADATA store (see docs/WEBAUTHN.md).
Use a server without METADATA only for trying ALPS out.
On Dovecot, METADATA is off until imap_metadata = yes is set for the IMAP protocol, and it also needs a mail attribute dictionary. Setup-Dovecot-and-Postfix has a working configuration.
Because the records live with the account, moving an account to another IMAP server keeps them only if the migration copies the private server METADATA under /private/vendor/alps/. A migration that copies only messages leaves the settings, identities and security keys behind.
ALPS reads the capabilities the server lists after LOGIN. Servers often list more after sign-in than before it, so check them after LOGIN (see the checklist).
| Capability | What ALPS uses it for | Without it |
|---|---|---|
METADATA or METADATA-SERVER
|
The per-account records | They last for one connection only (see above) |
UIDPLUS |
UID EXPUNGE, which removes only the messages being deleted, and the new UIDs of saved drafts and of moved or copied messages |
A permanent delete ends with a plain EXPUNGE, which also removes every other message in that folder marked \Deleted, including ones another mail app marked and could still undelete. The UID of a saved draft or sent copy is found by a STATUS before the APPEND and a search for its Message-ID after it. |
MOVE |
Moving messages between folders | COPY, then the delete described above |
| Capability | What ALPS uses it for | Without it |
|---|---|---|
THREAD=REFS, THREAD=REFERENCES or THREAD=ORDEREDSUBJECT
|
Conversations, with the first of these the server lists, in that order | Every listing is flat |
SORT |
Listing by message date | Flat listings, and searches sorted by date, come back in UID order, the order mail was filed in |
MULTISEARCH (RFC 7377) |
Search All: one search across the subscribed folders | No Search All, and the all-folders view opened from its URL is refused with HTTP 400. ESEARCH alone is not enough. |
LIST-EXTENDED or LIST-STATUS
|
Folders and subscriptions in one LIST, with the counts as well under LIST-STATUS | A plain LIST followed by an LSUB |
NAMESPACE |
Telling other people's and shared folders from the user's own, shown under "Shared folders" | Every folder counts as the user's own, and only top-level folders are looked at for Sent and Drafts |
ACL (RFC 4314) |
Sharing folders, and offering on other people's folders only the actions the user may take there | No sharing |
QUOTA (RFC 9208) |
The storage meter in the account menu | No meter |
PREVIEW (RFC 8970) |
A line of each message's text under its subject in the list | No previews |
BINARY (RFC 3516) |
Message parts decoded by the server | ALPS fetches parts as stored and decodes them itself |
SPECIAL-USE (RFC 6154) |
Asking for the special-use attributes in an extended folder listing | ALPS does not ask for them, and the page matches special folders by name where the server sends no attributes |
APPENDLIMIT (RFC 7889) |
The attachment limit the composer offers, and a size check before a draft is saved or a message is sent (see Message size limits) | ALPS learns that a message is too large only when the server refuses to store it |
Folder sharing also needs an other-users namespace in the server's NAMESPACE answer. The sharing levels and their rights are in docs/MAIL_SERVER.md.
A folder of more than 10,000 messages is never threaded, and without a search query it is listed by message number instead of with SORT. A server that refuses THREAD gets a flat list, and ALPS logs the refusal once per connection and folder.
These three are used only when the matching [server] key is set. All three keys default to false. Their costs are on Config-Server.
| Capability | Used with | What it does |
|---|---|---|
IDLE |
push_new_mail = true, with background_connection on (the default) |
Arrivals in and removals from the Inbox are pushed to open pages. Without it, pages check at the user's check-mail interval. |
NOTIFY (RFC 5465) |
push_subscribed_folders = true as well |
News of the subscribed folders, not only the Inbox |
CONDSTORE (RFC 7162) |
condstore = true |
A page whose messages are all still there has only its changed flags read again. ALPS turns it on with ENABLE where the server lists ENABLE. |
ALPS never enables QRESYNC, even where the server lists it.
Of the capabilities ALPS uses, a server that lists IMAP4rev2 counts as having NAMESPACE, UIDPLUS, ENABLE, IDLE, LIST-EXTENDED, LIST-STATUS and MOVE. It does not count as having SORT, THREAD, MULTISEARCH, BINARY, ACL, QUOTA, NOTIFY, CONDSTORE or METADATA: the server must list those itself. A server that lists QRESYNC counts as having CONDSTORE.
ALPS never creates special folders. The account must already have them, so configure the server to create them for every account (Dovecot: auto = create or auto = subscribe on each mailbox).
| Role | Attribute (RFC 6154) | Used for |
|---|---|---|
| Sent | \Sent |
ALPS files a copy of every sent message here |
| Drafts | \Drafts |
ALPS saves drafts here |
| Trash | \Trash |
Delete moves messages here |
| Archive | \Archive |
Archive moves messages here |
| Junk | \Junk |
Report Spam moves messages here |
| All | \All |
Shown as such in the sidebar |
The server side of ALPS finds Sent and Drafts itself, with a LIST of the user's own folders. Other people's folders and \Noselect placeholders never count.
- A folder with the
\Sentor\Draftsattribute is used first. - Otherwise ALPS takes a folder by name, in any case, after the personal namespace prefix (such as
INBOX.):Sentfor Sent, andDrafts, or elseDraft, for Drafts. - Without NAMESPACE, only top-level folders are looked at.
Without a Sent folder, the message still goes out, but no copy is kept. Once the SMTP server has accepted a message, a failure to file its copy in Sent, or to remove the draft it was sent from, does not turn the send into an error: the send is answered as done, with a warning, and the composer closes and says "Message sent, but no copy could be saved in Sent." (or that the draft could not be removed, and not to send it again). ALPS logs the cause as Sent message cleanup: filing=... draft=.... Without a Drafts folder, every draft save fails.
When the user opens a conversation, ALPS also searches Sent for the user's replies (by Message-ID, In-Reply-To and References headers), so that they show in the conversation.
The page itself picks the folders for Delete, Archive and Report Spam, and draws the special folders in the sidebar. It goes by the attributes in ALPS's folder listing first. For a role that no folder of the user's own carries as an attribute, it compares the folder's name, in any case, after the personal namespace prefix (such as INBOX.), or the whole name on a server without NAMESPACE:
| Role | Names the page matches | Folder named when none matches |
|---|---|---|
| Trash |
Trash, Deleted, Deleted Items
|
Trash |
| Archive |
Archive, Archives
|
Archive |
| Junk |
Junk, Spam
|
Junk |
| Sent | Sent |
|
| Drafts |
Drafts, Draft
|
When no folder has the role, the action names the fallback folder, and the server refuses the move if that folder does not exist. Folders in other people's namespaces never take a role.
When the server lists LIST-EXTENDED, LIST-STATUS or IMAP4rev2, ALPS builds its folder listing from one LIST "" "*" RETURN (SUBSCRIBED ...), and asks for SPECIAL-USE in it whenever the server lists the SPECIAL-USE capability. Otherwise it sends a plain LIST. Mark the special folders with special-use attributes on the server: a folder that carries one is found by it, whatever its name. Step 4 of the checklist shows what ALPS's listing gets.
[smtp] server is required for every provider, maildir and multi included. Without it ALPS stops at startup with no SMTP server specified in config file ([smtp] server).
| Scheme | Transport | Default port |
|---|---|---|
smtps:// |
Implicit TLS | 465 |
smtp:// |
STARTTLS, required. The send fails if the server does not offer it. | 587 |
smtp+insecure:// |
Plaintext | 587 |
Certificates are checked against the host's system roots. [smtp] insecure = true turns smtp:// into plaintext and never changes smtps://. A value without a scheme, such as smtp.example.com:587, stops startup with SMTP server requires a scheme (smtps://, smtp://, smtp+insecure://).
ALPS keeps no SMTP connection open. For each send it connects, says EHLO localhost (STARTTLS first, for smtp://), and authenticates with AUTH PLAIN, using the username and password the user signed in with. It sends AUTH without looking at the server's EHLO list. It then sends MAIL FROM with the address chosen in the composer's From (with the message's size, when the server lists SIZE), one RCPT TO per To, Cc and Bcc recipient, the message and QUIT. After a failed command, ALPS still says QUIT before it closes the connection.
So the submission server must:
- accept
AUTH PLAINon that connection (after STARTTLS forsmtp://) with the same username and password as the IMAP server. - let each login send as its own address and as each identity the user adds in Settings > Identity. ALPS checks that the From is the account's address or an identity; the submission server decides which of those the login may really use. When it refuses an identity for good, the composer says so and the user can pick another From.
- not file its own copy of submitted mail into Sent. ALPS files the copy itself over IMAP, and a second copy would show every sent message twice.
The message goes out without a Bcc header; Bcc recipients are only in the SMTP envelope. The copy in Sent is the message as submitted, so it has no Bcc header either. Drafts keep theirs.
A send is tried up to three times, on a fresh connection each time, after a failed connection or a temporary (4xx) reply. Refused credentials (any error reply to AUTH) get 403, and a permanent (5xx) reply is not tried again. Timeouts and the full retry rules are in docs/MAIL_SERVER.md. Calendar invitations that ALPS mails itself go the same way.
With the multi provider, each backend can name its own submission server; see Config-Providers-and-SMTP and Setup-Many-Domains.
ALPS writes attachments into the message in base64, so a message is about a third larger than its files. The submission server must accept it, and the IMAP server must accept an APPEND of it, since ALPS stores drafts in Drafts and the sent copy in Sent.
ALPS reads the limits the servers advertise:
- the IMAP server's
APPENDLIMIT, as a capability, or as theSTATUSlimit of the account's Drafts and Sent folders; - the submission server's
SIZE. To read it, ALPS opens a short connection without signing in (EHLO, after STARTTLS forsmtp://), at most once every ten minutes per session.
The composer's attachment limit is then the smaller of [server] max_attachment_mib and what fits under those limits, after 64 KiB for the headers and text and the room base64 takes. A 25 MiB limit leaves about 18 MiB for attachments. Before a draft is saved or a message sent, ALPS checks the whole encoded message against the known limits, including whether its copy fits in Sent, so a message is not sent when its copy could not be kept. A message the servers refuse as too large (NO [TOOBIG] from IMAP, 552 from SMTP) gets 413 message_too_large, and the composer keeps its contents and asks the user to remove attachments or shorten the message. An APPENDLIMIT of 0 forbids storing messages, so the composer offers no attachments and ALPS refuses to send. The details are in docs/ATTACHMENTS.md.
These servers are needed only for the plugins that use them (see Plugins). ALPS signs in to the ManageSieve server with the username and password the user typed on the sign-in page, so it must accept the same credentials as the mail server. The CalDAV and CardDAV servers you configure get the same credentials by default, so they must accept them too. In Settings > Calendar and Settings > Contacts, each user can instead save a separate username and password for those servers, or a CalDAV or CardDAV server of their own.
CalDAV and CardDAV (CDAV.md):
- ALPS sends every request with HTTP Basic authentication. Use TLS endpoints (
https://,caldavs://,carddavs://); certificates are checked against the host's system roots. - CardDAV: ALPS uses only the first address book the server lists in the user's home set, and never creates one, so the server must provide one for each account. Without one, Contacts lists nothing, and saving a contact is refused with 409
no_address_book. - CalDAV: ALPS reads and writes the user's calendars, and creates a calendar when the user asks for one. A
[plugin.caldav] servergiven as a bare domain is looked up once at startup through DNS SRV (_caldavs._tcp). - At startup, ALPS sends an
OPTIONSrequest to each global DAVserver. A server that does not answer is logged (... (continuing anyway)), and ALPS starts all the same. A connection a user saves in Settings is checked when it is saved instead: ALPS looks up the user's calendar or address-book home with the credentials given, and does not save a connection that fails. - A server a user enters in Settings must be given with
https://, orcaldavs://orcarddavs://. ALPS connects to it only at public addresses.[server] dav_allow_private_servers = truelifts both limits, for servers on a trusted internal network (see CDAV.md).
ManageSieve (FILTERS.md):
- Port 4190 unless the URL names another. ALPS upgrades the connection with STARTTLS and checks the certificate before it signs in, and does not sign in when the server offers no STARTTLS (unless the URL uses a
+insecurescheme). - ALPS signs in with SASL PLAIN.
- The auto-reply needs the
vacationextension in the server'sSIEVEcapability, and its start and end dates also needdateandrelational. - ALPS checks a script before it sends it, and the server checks it too: on Save as it stores the script, and on Validate with
CHECKSCRIPTwhen the server listsVERSION(RFC 5804). A script that needs an extension the server lists but ALPS's own check does not know, such asinclude, is left to the server to judge.
ALPS treats NO [LIMIT] (RFC 5530) as the server asking it to back off, not as a failed request:
- a sign-in refused this way gets
connection_limit, and the sign-in page says that too many connections are open to the account. - a session whose reconnect is refused this way carries on with its background connection when it has one.
- a search whose SEARCH, SORT or ESEARCH is refused this way gets HTTP 429
search_rate_limited, withRetry-Afterwhen the server's text names a wait such aswait 42s. - a folder listing without a query is never refused this way: ALPS lists the folder by message number, or flat, instead.
- a listing, with a query or without, whose session ends because its reconnect was refused this way (with no background connection to carry on with) gets 503
mail_server_unavailable, as every request of that session does.
Details are in docs/MAIL_SERVER.md.
Run these with a test account, from the host ALPS runs on, so that the same address, DNS and certificate roots are used. In the blocks that are not shell commands, each line is what you type into the open session.
-
Connect the way ALPS will. For
imaps://:openssl s_client -quiet -crlf -verify_return_error -verify_hostname imap.example.com -connect imap.example.com:993
For
imap://, end the command with-starttls imap -connect imap.example.com:143instead. With-verify_return_errorand-verify_hostname, the connection stops withcertificate verify failedwhen the chain does not verify against the system roots or the certificate does not name the host. ALPS checks both, so such a certificate does not work with ALPS either. Without these two options,verify return:1is printed even after a failed check, so that line alone proves nothing. (Theopensslthat ships with macOS is LibreSSL, which has no-verify_hostname.) -
Sign in and read the capabilities, with the username as users will type it on ALPS's sign-in page:
a LOGIN user@example.com password b CAPABILITYIf the username or password holds a space or punctuation, put it in double quotes, with a backslash before any
"or\in it. Look forIMAP4rev1orIMAP4rev2,METADATAorMETADATA-SERVER,UIDPLUSandMOVE(IMAP4rev2 includes these two), then the optional ones you want: aTHREAD=algorithm,SORT,MULTISEARCH,ACL,QUOTA,PREVIEW. -
Check that METADATA keeps a private server entry, then remove it:
c SETMETADATA "" (/private/comment "alps check") d GETMETADATA "" /private/comment e SETMETADATA "" (/private/comment NIL)dmust answer* METADATA "" (/private/comment ...)with the value. -
Check the folders and the namespace:
f NAMESPACE g LIST "" "*" RETURN (SUBSCRIBED SPECIAL-USE) h LOGOUTgis the listing ALPS asks for on a server that listsSPECIAL-USEand one of LIST-EXTENDED, LIST-STATUS and IMAP4rev2. LeaveSPECIAL-USEout of it when the server does not list that capability, and sendg LIST "" "*"when it lists none of the other three. Sent and Drafts must exist, by attribute or by name, and Trash, Junk and Archive should. A folder thatgshows without its attribute is found only by a name from the table above. -
Check submission. For
smtp://:openssl s_client -quiet -crlf -verify_return_error -verify_hostname smtp.example.com -starttls smtp -connect smtp.example.com:587
For
smtps://, end the command with-connect smtp.example.com:465, without-starttls. Then:EHLO localhost AUTH PLAIN <base64> MAIL FROM:<identity@example.com> RCPT TO:<user@example.com> RSET QUIT<base64>is the output ofprintf '\0%s\0%s' 'user@example.com' 'password' | base64.AUTHmust answer235. Run theMAIL FROMandRCPT TOpair for the account's address and for each address users will add as an identity, withRSETbetween pairs: both must answer250. Some submission servers, Postfix among them, check the sender against the login only once a recipient is named, and then refuse theRCPT TO.RSETends the transaction before anything is sent. The password ends up in your shell history this way, so use a test account. -
Check the limits and the server's own filing. The per-user and per-address connection limits must leave room for ALPS (see How many connections ALPS opens), and the submission server must not save its own copy of submitted mail into Sent.
Then sign in to ALPS, change a setting, sign out and sign in again: the setting must still be there. If ALPS logs using transient store instead, the server listed neither METADATA nor METADATA-SERVER after LOGIN.
ALPS's browser tests run against sora, an IMAP server with METADATA (see Browser-Tests). For a complete Dovecot and Postfix example, see Setup-Dovecot-and-Postfix.
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