Repository navigation
Setup Local Try Out
This page runs ALPS on your own computer against a mailbox you already have, so you can see it work before you deploy it anywhere. You build ALPS (or run it with go run), give it a short config.toml that names your provider's IMAP and SMTP servers, sign in, and check that settings are kept and mail goes out. The last part adds the Vite dev server, for work on the interface. Nothing here is meant for other people to use: for that, see Running-in-Production and Setup-Behind-a-Reverse-Proxy.
- ALPS at
http://localhost:1323, reachable from this computer only. - Your mail, read from the provider's IMAP server and sent through its SMTP submission server with your own username and password. ALPS has no database: it keeps your settings on the IMAP server, as IMAP METADATA (see Storage-in-IMAP-METADATA).
- The mail interface with its Settings window and the OpenPGP keyring (Settings > GPG Keys). Contacts, calendars, filters and password change stay off until you configure them.
- Optionally, the Vite dev server at
http://localhost:5173in front of it. It serves the interface fromfrontend/srcand reloads the page when you save a file.
-
Go 1.25.0 or later.
go.modnamestoolchain go1.26.5. With the defaultGOTOOLCHAIN=auto, an oldergocommand (Go 1.21 or later) downloads go1.26.5 and builds with it. -
git and make.
-
Linux, macOS, a BSD or Windows. On Windows,
[logging] output = "syslog"falls back to stderr, and the password plugin'scommandbackend is refused. This page was tried on macOS only. -
Node.js and npm, only for the dev server and for rebuilding
frontend/dist: Node.js 20 (20.19 or later), 22 (22.12 or later), or 24 and later. Building and running ALPS does not need them, because the binary carries the compiled interface. -
A mailbox that accepts its password over IMAP and SMTP. ALPS signs in to IMAP with the
LOGINcommand and to SMTP withAUTH PLAIN, using the username and password typed on its sign-in page. It does not use OAuth. You need the provider's IMAP and SMTP host names and ports; this page writes them asimap.example.comandsmtp.example.com. -
METADATA on the IMAP server, to keep settings. Without it ALPS still works, but anything you change in Settings is gone at the next sign-in. To check, connect with
openssl:openssl s_client -quiet -crlf -connect imap.example.com:993
and type these lines, with your own address and password:
a LOGIN "you@example.com" "your password" b CAPABILITY c LOGOUTLook for
METADATAorMETADATA-SERVERin the answers toaandb. A server often lists more afterLOGINthan in its greeting. Ifopenssldoes not exit afterc LOGOUT, press Ctrl-C. Mail-Server-Requirements explains the rest of the list.
Get the code:
git clone https://github.com/migadu/alps.git
cd alpsCreate config.toml in the root of the clone. Git ignores that file name.
[server]
# This computer only. Without this line ALPS listens on ":1323", on every interface.
addr = "127.0.0.1:1323"
[provider.imap]
server = "imaps://imap.example.com:993"
[smtp]
server = "smtps://smtp.example.com:465"
# Load these plugins and no others.
[plugin.base]
enabled = true
[plugin.gpg]
enabled = trueWhat each part does:
-
[provider.imap] serveris your IMAP server.imaps://is implicit TLS (default port 993).imap://requires STARTTLS (default port 143). ALPS checks the certificate against the system's trusted roots.[provider] typecan stay out:"imap"is the default. -
[smtp] serveris the submission server, and ALPS refuses to start without it.smtps://is implicit TLS (default port 465).smtp://requires STARTTLS (default port 587). -
The plugin list.
base, which gives sign-in and mail, always loads. Once one[plugin.*]section setsenabled = true, the sections that set it form a list, and onlybaseand the plugins on that list load, sogpgis listed here. A section that leavesenabledout is then left out too, andenabled = falsealways keeps its plugin out. To add contacts, calendars or filters from your provider, add[plugin.carddav],[plugin.caldav]or[plugin.managesieve]withenabled = trueand aserver, as Config-Plugins describes. CardDAV and CalDAV also load withenabled = trueand noserver: you then enter a server of your own under Settings > Contacts or Settings > Calendar. ALPS reaches such a server over HTTPS and at a public address only, unless[server] dav_allow_private_servers = true(CDAV.md). Settings shows a Password section only once[plugin.password]setsenabled = trueand an endpoint or a command (Setup-Password-Change).
Every other key keeps its default. Configuration-Overview and config.example.toml list them all. ALPS ignores keys it does not know, keeps their defaults, and names each one in a warning at startup, such as Unknown configuration key: server.write_timeout (ignored). Check the startup log for such lines after each change.
These defaults decide how the try-out behaves:
-
No
login_key. Sessions live in ALPS's memory, so after a restart you sign in again. "Stay signed in after closing the browser" then keeps you signed in only while that session lasts: it ends after 30 idle minutes by default, or when ALPS stops. Linked Accounts need alogin_key. The dev-server section below adds one, and Sessions-and-Sign-in explains it. -
No
[webauthn]section. The relying party islocalhost, with the originshttp://localhost:1323andhttp://localhost:5173. Security keys therefore work when you open ALPS ashttp://localhost:1323, not ashttp://127.0.0.1:1323. - Sign-in rate limits are on. One address gets five sign-in attempts a minute, successful ones included (see Config-Rate-Limiting-and-WebAuthn).
-
debugis off. Leave it off unless you are chasing a sign-in problem (see Troubleshooting).
Build a binary and start it:
make build
./build/alps -config config.tomlmake build writes build/alps and stamps it with the version, commit and build date, which ./build/alps -version prints, for example alps v1-1725-ga5443047 (a5443047) built at 2026-11-12T09:58:31Z. The binary contains frontend/dist as it was at build time. make help lists the other targets.
Or compile and start in one step:
go run ./cmd/alps -config config.tomlgo run compiles again on every start, and does not stamp the version: go run ./cmd/alps -version prints alps unknown (unknown) built at unknown.
ALPS logs JSON lines to stdout. A good start looks like this (the time field is left out here):
{"level":"INFO","msg":"Configured SMTP server: smtps://smtp.example.com:465"}
{"level":"INFO","msg":"Rate limiting enabled: IP=5/min, 20/hour | User=5/15min, 10/hour | Global=100/sec"}
{"level":"INFO","msg":"Loaded plugin \"base\""}
{"level":"INFO","msg":"Loaded plugin \"gpg\""}
{"level":"WARN","msg":"WebAuthn: [webauthn] rpid is unset, defaulting to \"localhost\" — 2FA will not work unless served from localhost; set rpid and origins to your public hostname"}
{"level":"INFO","msg":"Starting server on 127.0.0.1:1323"}
The WebAuthn warning is expected here, since you open ALPS as localhost. Ctrl-C stops ALPS.
In a browser:
-
Sign in. Open
http://localhost:1323and sign in with the username and password your provider gives for IMAP. The Inbox opens, with your folders on the left. The log shows asign-inline with your username. - Check that settings are kept. Open the menu next to your address in the top right and choose Settings. Under General, change Check mail interval, for example from "Every 5 minutes" to "Every 15 minutes". The change is saved at once. Close the Settings window (its X, or Escape), since it covers the menu, then choose Sign Out from the same menu. Sign in again and open Settings: the new interval is still there. That value went to the IMAP server and came back. If it is back at the old value, see Troubleshooting.
- Send yourself a message. Click Compose, put your own address in To (press Enter to turn it into a recipient), add a subject and some text, and click Send. A "Message is being sent..." notice with Undo stays for a few seconds, and then the message goes. Reload the page: the message is in the Inbox and a copy in Sent.
From a terminal, the same sign-in and folder list (the password is read without echo, so it stays out of your shell history):
read -rs ALPS_PASS # type the password, then Enter
curl -s -c alps-cookies.txt http://localhost:1323/session \
-H 'Origin: http://localhost:1323' \
--data-urlencode 'username=you@example.com' \
--data-urlencode "password=$ALPS_PASS"
# {"ok":true}
curl -s -b alps-cookies.txt http://localhost:1323/mailboxes | grep -oE '"Mailbox":"[^"]+"'
# "Mailbox":"INBOX", then one line for each of your other folders
curl -s -b alps-cookies.txt -X DELETE http://localhost:1323/session -H 'Origin: http://localhost:1323'
rm alps-cookies.txt; unset ALPS_PASSALPS checks the Origin of every sign-in, change and sign-out, so give the address you reach it at. While these run, the log shows a request line for each request, with its status and your username.
For work on the interface or on a plugin's frontend, run the Vite dev server in front of ALPS.
-
Keep ALPS on port 1323. The dev server forwards the API paths to
http://localhost:1323, as set in frontend/vite.config.ts. Theaddr = "127.0.0.1:1323"above works with it. -
Optionally, stay signed in across backend restarts. Generate a key with
openssl rand -base64 32and add it to the[server]section:[server] addr = "127.0.0.1:1323" login_key = "PASTE-THE-OUTPUT-OF-openssl-rand-base64-32"
Replace the whole quoted value with the key. Keep the key to yourself, as it decrypts the login cookie. After you restart ALPS, the next request from your browser opens a new session from that cookie, and the log says
session restored. The key also lets you try Linked Accounts. -
Start the dev server in a second terminal:
cd frontend npm install npm run devOpen
http://localhost:5173and sign in. Vite serves the interface from the source files and picks up each file you save. Savingsrc/utils/flags.ts, for example, reloads the open page, and Vite printspage reload src/utils/flags.ts.
While you work through the dev server, ALPS logs a line like CSRF: Allowing localhost origin mismatch in development: got "http://localhost:5173", expected "http://localhost:1323" for each change you make. That is expected: the dev server forwards requests under the backend's address. The dev server also forwards /events, so new-mail push reaches the page when [server] push_new_mail = true. A [branding] section is not applied through the dev server, because Vite serves its own index.html.
When the change is done, npm run build rewrites frontend/dist, and make build puts it into the binary. A change to the interface should include the rebuilt frontend/dist. Run the frontend unit tests before you send it: npm test typechecks the tests first, then runs them (see Frontend-Unit-Tests and Testing-Overview).
ALPS stops at startup:
| Message | Fix |
|---|---|
Error: -config flag is required |
Start it with -config config.toml. |
Error generating options: no SMTP server specified in config file ([smtp] server) |
Add the [smtp] section with server. |
Error generating options: IMAP server requires a scheme (imaps://, imap://, imap+insecure://), got: imap.example.com:993 |
Write the scheme: imaps://imap.example.com:993. |
SMTP server requires a scheme (smtps://, smtp://, smtp+insecure://), got: smtp.example.com:465, as an ERROR line in the log |
Write the scheme: smtps://smtp.example.com:465. |
Error generating options: invalid login key |
The value must be 32 bytes in base64 or hex. Paste the output of openssl rand -base64 32, not the placeholder. |
HTTP listener failed: listen tcp 127.0.0.1:1323: bind: address already in use, as an ERROR line after Starting server on 127.0.0.1:1323
|
Another program, often an ALPS you started earlier, holds the port. Stop it, or pick another port in addr if you do not use the dev server, which expects 1323. |
Sign-in fails:
-
"Login failed. Please check your credentials." The API answers 401
{"error":"Failed to login"}, and the log showssign-in failedwith"reason":"bad credentials"and the IMAP server's answer in"error", such asauthentication failed: .... The IMAP server refused the username or password. Use the login your provider gives for IMAP, which is often the full address. Try it with theopensslcheck above. -
503
{"code":"service_unavailable","error":"Mail service temporarily unavailable"}. The log showssign-in failedwith"reason":"error"and the cause, for examplefailed to connect to IMAP server: dial tcp ...: connect: connection refused. Check the host, the port and the scheme, and whether this computer can reach the server at all. A server that takes passwords only over TLS answers this way too when the URL isimap+insecure://(it refuses theLOGINwithPRIVACYREQUIRED, or listsLOGINDISABLED). -
Too many login attempts, please wait a minute. The API answers 429 withRetry-After: 60. One address gets five sign-in attempts a minute, successful ones included, and the next is refused. Wait a minute. Scripts that sign in over and over reach the limit first. -
403
Invalid originfrom curl. TheOriginheader does not match the address you used, and the log showsCSRF: Origin mismatch: got ..., expected .... Send the address exactly as you reach ALPS, scheme and port included.
After signing in:
-
Settings are back to their defaults at the next sign-in. The IMAP server has no METADATA. ALPS logs this once, as an INFO line like the others:
alps/provider: IMAP server doesn't support the METADATA extension, using transient store instead. Settings, identities and the OpenPGP keyring then last only for one IMAP connection. Use a server that offers METADATA (see Storage-in-IMAP-METADATA and Setup-Dovecot-and-Postfix). -
Signed out after restarting ALPS. Without
login_key, sessions live in memory only, so sign in again. Your settings are not lost: they are on the IMAP server. -
Linked Accounts answers
Account linking requires login key to be configured. Add alogin_keyas in Frontend dev server.
To see the IMAP exchange, set debug = true in [server] and start ALPS again. The log then becomes console text at debug level, and carries the IMAP exchange as protocol trace lines with protocol=imap, the password shown as [REDACTED]. The traces are debug lines, so a [logging] section with level = "info" leaves them out. They also hold message content, so turn debug off again when you are done, and never use it in production.
npm warnings. With Node.js 23, npm install warns EBADENGINE Unsupported engine for vitest, which declares ^20.0.0 || ^22.0.0 || >=24.0.0. The dev server still ran, but use Node.js 22 or 24 for the unit tests. On macOS, npm 11.19 warns that fsevents has install scripts not covered by allowScripts. The dev server ran with that warning too.
Tested on 2026-11-12 with ALPS main a5443047, built with make build and also started with go run, using Go 1.26.5 (darwin/arm64) and GNU Make 3.81 on macOS 26.4 (arm64). The dev server ran on Node.js 24.22.1 with npm 11.19.1 and Vite 8.0.10. The browser steps ran in Chrome for Testing 151.0.7922.34 (headless, driven by Playwright 1.62.0). The mail servers were sora 22ce317 (IMAP with METADATA, and SMTP submission) and, for the case without METADATA, Dovecot 2.4.5. In the lab, ALPS listened on port 9000 and the dev server on 9001 instead of 1323 and 5173. The mail servers were on loopback without TLS (imap+insecure://, smtp+insecure://) instead of imaps:// and smtps://.
Updated from the code at main 92ec0e11 without a rerun: the plugin rules and the order of the startup lines, CalDAV and CardDAV servers entered in Settings, the startup warning for unknown keys, the SMTP scheme and listener errors, the cause logged for a refused sign-in, the protocol traces of debug, which now go through the log, and the $hasattachment keywords, which ALPS no longer shows as tags. The configuration file above was started once with a 92ec0e11 build on macOS, to check that ALPS knows every key and loads the plugins shown.
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