Skip to content

Mail Server Requirements

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

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.

At a glance

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.

Connecting and signing in to IMAP

[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.

How many connections ALPS opens

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.

METADATA: where ALPS keeps its records

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.

IMAP capabilities

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).

Required and recommended

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

Used when present

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.

Off unless you turn them on

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.

What IMAP4rev2 counts for

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.

Special-use folders

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

Sent and Drafts

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.

  1. A folder with the \Sent or \Drafts attribute is used first.
  2. Otherwise ALPS takes a folder by name, in any case, after the personal namespace prefix (such as INBOX.): Sent for Sent, and Drafts, or else Draft, for Drafts.
  3. 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.

Trash, Archive, Junk and the sidebar

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 submission

[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 PLAIN on that connection (after STARTTLS for smtp://) 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.

Message size limits

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 the STATUS limit 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 for smtp://), 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.

CalDAV, CardDAV and ManageSieve

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] server given as a bare domain is looked up once at startup through DNS SRV (_caldavs._tcp).
  • At startup, ALPS sends an OPTIONS request to each global DAV server. 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://, or caldavs:// or carddavs://. ALPS connects to it only at public addresses. [server] dav_allow_private_servers = true lifts 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 +insecure scheme).
  • ALPS signs in with SASL PLAIN.
  • The auto-reply needs the vacation extension in the server's SIEVE capability, and its start and end dates also need date and relational.
  • 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 CHECKSCRIPT when the server lists VERSION (RFC 5804). A script that needs an extension the server lists but ALPS's own check does not know, such as include, is left to the server to judge.

When the server says [LIMIT]

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, with Retry-After when the server's text names a wait such as wait 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.

Checking a server

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.

  1. 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:143 instead. With -verify_return_error and -verify_hostname, the connection stops with certificate verify failed when 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:1 is printed even after a failed check, so that line alone proves nothing. (The openssl that ships with macOS is LibreSSL, which has no -verify_hostname.)

  2. 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 CAPABILITY
    

    If the username or password holds a space or punctuation, put it in double quotes, with a backslash before any " or \ in it. Look for IMAP4rev1 or IMAP4rev2, METADATA or METADATA-SERVER, UIDPLUS and MOVE (IMAP4rev2 includes these two), then the optional ones you want: a THREAD= algorithm, SORT, MULTISEARCH, ACL, QUOTA, PREVIEW.

  3. 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)
    

    d must answer * METADATA "" (/private/comment ...) with the value.

  4. Check the folders and the namespace:

    f NAMESPACE
    g LIST "" "*" RETURN (SUBSCRIBED SPECIAL-USE)
    h LOGOUT
    

    g is the listing ALPS asks for on a server that lists SPECIAL-USE and one of LIST-EXTENDED, LIST-STATUS and IMAP4rev2. Leave SPECIAL-USE out of it when the server does not list that capability, and send g 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 that g shows without its attribute is found only by a name from the table above.

  5. 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 of printf '\0%s\0%s' 'user@example.com' 'password' | base64. AUTH must answer 235. Run the MAIL FROM and RCPT TO pair for the account's address and for each address users will add as an identity, with RSET between pairs: both must answer 250. Some submission servers, Postfix among them, check the sender against the login only once a recipient is named, and then refuse the RCPT TO. RSET ends the transaction before anything is sent. The password ends up in your shell history this way, so use a test account.

  6. 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.

Clone this wiki locally