add docker setup

This commit is contained in:
2026-08-15 06:22:02 +01:00
parent 892f366a16
commit 310700407e
11 changed files with 410 additions and 19 deletions
+9
View File
@@ -0,0 +1,9 @@
.git
.claude
graphify-out
tests
server_data
ssl_certs
*.db
*.crt
*.key
+112 -6
View File
@@ -1,4 +1,89 @@
# Deploying mailgoserver # mailgoserver
A self-hosted email server in one static Go binary: SMTP (send + receive, direct-to-MX
delivery, DKIM signing), IMAP mailbox storage, an admin web dashboard, and a self-service
webmail portal. It's a Go port of PyMTA-server — same feature set, one process instead of
a Python venv + separate services.
## What it does
- **SMTP MTA** — accepts mail for domains you configure, relays outbound mail directly
to the recipient's MX (no smarthost needed), signs outgoing mail with DKIM, and
enforces per-domain sender authentication or IP whitelisting so it can't be used as an
open relay.
- **IMAP mailbox storage** — real, retrievable mailboxes (Thunderbird, Outlook, any IMAP
client) with AES-encrypted-at-rest message storage, per-mailbox quotas, send-as
aliases, and filter rules (move to folder, forward, discard, based on from/subject/
body conditions with AND/OR logic).
- **Spam filtering** — a built-in heuristic score always runs; optionally point it at an
[rspamd](https://rspamd.com) instance for a lot more signal (see `docker-deploy/` for
a container that bundles rspamd for you).
- **Webmail portal** (`/webmail`) — inbox/folders, HTML compose with attachments,
drafts, search, keyboard shortcuts, conversation grouping, recipient autocomplete,
filter-rule management, and PGP (OpenPGP encrypt/decrypt/sign/verify) and S/MIME
(sign/verify) support per mailbox.
- **Admin dashboard** (`/pymta-manager`) — manage domains, senders, mailboxes, DKIM
keys, IP whitelisting, TLS (self-signed or Let's Encrypt via DNS-01: Cloudflare,
Route53, DigitalOcean, Google Cloud DNS), and review email + auth logs.
- **Security hardening built in** — CSRF protection, security headers (CSP, X-Frame-
Options, etc.), Cloudflare-aware trusted-proxy IP resolution, login rate limiting and
account lockout, TOTP + WebAuthn/passkey MFA (admin and mailbox owners), and automatic
temporary IP blacklisting for SMTP/IMAP brute-force or relay-abuse attempts with an
admin-visible Blacklist page and dashboard attack-count tiles.
## Quick start
### Docker (fastest)
```bash
cd docker-deploy
docker compose --profile standalone up -d --build
```
See [`docker-deploy/README.md`](docker-deploy/README.md) for the rspamd-bundled variant
and full details (ports, volumes, first-boot config).
### From source
```bash
go build -o mailgoserver .
./mailgoserver
```
One static binary — no venv, no `pip install`, no gunicorn. Requires Go 1.26+ to build;
the binary itself has no runtime dependencies (pure-Go SQLite driver, no cgo).
## What to expect on first run
There's no config to write up front. The first time it starts in a given working
directory, it generates:
- `settings.ini` — every setting with an inline comment explaining it (SMTP/IMAP ports,
hostname, TLS, DKIM key size, mailbox quotas, MFA enforcement, rate limits, and so
on). Regenerated only if missing — it's never overwritten or merged into on later
runs, so edits stick.
- A self-signed TLS certificate (used until you either supply your own or enable Let's
Encrypt from the admin dashboard).
- An empty SQLite database, with one seeded admin account: username `admin`, password
`Password123!`. Logging in with it **immediately forces** a username + password
change before anything else in the dashboard is reachable — the default credentials
can never be left in place.
- A mailstore master key and a CSRF-signing app secret, both generated once and reused
on every subsequent start — back these up like any other secret (losing the mailstore
master key makes all stored mail unrecoverable, even for admins).
From there: log into `/pymta-manager`, add a domain (and complete its DNS ownership
verification), add a mailbox or sender, and you're sending/receiving. The bare `/` root
redirects to the webmail login (`/webmail/login`) by default, since most visitors are
mailbox owners, not admins — there's a "Login as Admin" link from there to
`/pymta-manager`.
Default ports (all configurable in `settings.ini`): SMTP `25`, direct-TLS SMTP `465`,
IMAP `143`, direct-TLS IMAP `993` — the real standard mail ports, so binding them
directly needs root or `setcap` (see below), which the Docker deployment already
handles for you. Admin/webmail HTTP `5000` / HTTPS `5001` stay deliberately
non-privileged; put a reverse proxy or your own `80`/`443` mapping in front of those if
you want the dashboard on standard web ports too.
## Build ## Build
@@ -7,15 +92,20 @@ cd mailgoserver
go build -o mailgoserver . go build -o mailgoserver .
``` ```
One static binary — no venv, no `pip install`, no gunicorn. ## Bind ports 25/143/465/993 without root
## Bind ports 25/587 without root The default SMTP/IMAP ports are the real standard ones now, so running the binary
directly (not via Docker) needs one of:
```bash ```bash
sudo setcap 'cap_net_bind_service=+ep' ./mailgoserver sudo setcap 'cap_net_bind_service=+ep' ./mailgoserver
``` ```
Same purpose as `script_setup_py_environment.sh`'s `setcap` step on the Python venv, applied to the compiled binary instead. or run it as root, or via the systemd unit below (which grants the capability instead
of running as root). Same purpose as `script_setup_py_environment.sh`'s `setcap` step
on the Python venv, applied to the compiled binary instead. **Not needed for the Docker
deployment** — those containers run as root, so binding 25/143/465/993 directly just
works with no extra setup.
## systemd (unified process) ## systemd (unified process)
@@ -56,6 +146,12 @@ and terminates its own TLS for the web UI — mailgoserver listens on the same h
by default, so the existing nginx config works unmodified. The SMTP TLS listener still by default, so the existing nginx config works unmodified. The SMTP TLS listener still
consumes `ssl_certs/server.crt`/`server.key`, same as before. consumes `ssl_certs/server.crt`/`server.key`, same as before.
## Docker
See [`docker-deploy/`](docker-deploy/) — a standalone image and one that bundles the
latest rspamd in the same container, both via a single `docker-compose.yml` using
Compose profiles.
## Admin dashboard login ## Admin dashboard login
First run seeds one account: username `admin`, password `Password123!`. Logging in First run seeds one account: username `admin`, password `Password123!`. Logging in
@@ -70,10 +166,20 @@ Optional second factors, enabled per-account from **Account** in the sidebar:
`rp_origin = https://mail.example.com`). The defaults (`localhost` / `rp_origin = https://mail.example.com`). The defaults (`localhost` /
`http://localhost:5000`) only work for local testing — WebAuthn requires either `http://localhost:5000`) only work for local testing — WebAuthn requires either
HTTPS or the literal host `localhost`, so passkeys need the nginx+TLS setup above HTTPS or the literal host `localhost`, so passkeys need the nginx+TLS setup above
to work behind a real domain. (or the Docker deployment) to work behind a real domain.
## Abuse protection
Failed SMTP/IMAP auth attempts (bad passwords, denied relay attempts) are counted per
source IP; once a configurable threshold is hit within a window, that IP is temporarily
blocked at the listener level — before the SMTP/IMAP banner is even sent — with the
block duration doubling on repeat offenses up to a cap. Manage active blocks and exempt
trusted IPs from this check entirely from **Blacklist** in the admin sidebar (a global-
admin-only page) — this is separate from the IP whitelist used to authorize
unauthenticated relay for a domain. Tunable in `settings.ini`'s `[Security]` section.
--- ---
## License ## License
See [LICENSE](LICENSE). See [LICENSE](LICENSE).
+9
View File
@@ -0,0 +1,9 @@
# Copy to .env (in this docker-deploy/ folder) and adjust if the defaults below clash
# with something else on the host, or if you want to run both the "standalone" and
# "with-rspamd" profiles side by side (give each its own set of host ports).
SMTP_PORT=25
SMTP_TLS_PORT=465
IMAP_PORT=143
IMAP_TLS_PORT=993
WEB_HTTP_PORT=5000
WEB_HTTPS_PORT=5001
+50
View File
@@ -0,0 +1,50 @@
# syntax=docker/dockerfile:1
#
# Standalone mailgoserver image — SMTP + IMAP + admin dashboard + webmail, no rspamd.
# The built-in heuristic spam score (internal/mailstore/spam.go) always runs regardless;
# this is for anyone who doesn't want rspamd's extra dependency. See Dockerfile.rspamd
# for the bundled variant.
#
# Build from the repo root:
# docker build -f docker-deploy/Dockerfile -t mailgoserver .
FROM golang:1.26-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# modernc.org/sqlite and every other dependency here are pure Go (no cgo), so a fully
# static binary is just CGO_ENABLED=0 — no libc/gcc needed in the runtime image.
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mailgoserver .
FROM debian:bookworm-slim
# ca-certificates: outbound direct-to-MX delivery verifies remote STARTTLS certs.
# tzdata: [Server] time_zone (e.g. "Europe/London") needs the real IANA zone
# database — time.LoadLocation silently falls back to UTC without it.
# curl: used by the HEALTHCHECK below.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates tzdata curl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver
# Everything persistent — the auto-generated settings.ini, the SQLite DB, encrypted
# mailbox storage, DKIM/mailstore master keys, TLS certs, the app secret — lives under
# whatever directory the process is started from (see config.Load / main.go's
# os.Getwd()). One volume here covers all of it; no need to enumerate subpaths.
WORKDIR /app/data
VOLUME ["/app/data"]
# Defaults from internal/config/config.go's generated settings.ini: SMTP 25, direct-TLS
# SMTP 465, IMAP 143, direct-TLS IMAP 993 (the actual standard ports — binding them
# needs no setcap/capability here since this container runs as root), admin/webmail
# HTTP 5000, HTTPS 5001 (deliberately non-privileged; put a reverse proxy or the host's
# own 80/443 in front if you want those too).
EXPOSE 25 465 143 993 5000 5001
# --host 0.0.0.0 is required: the binary's own default is 127.0.0.1, which would only
# be reachable from inside this container, never through a published port.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fs http://127.0.0.1:5000/health || exit 1
ENTRYPOINT ["mailgoserver", "--host", "0.0.0.0"]
+47
View File
@@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1
#
# mailgoserver + the latest rspamd, bundled in one container. Both processes share this
# container's network namespace, so rspamd's default "normal" worker
# (127.0.0.1:11333, the /checkv2 scanning API — see internal/mailstore/rspamd.go) is
# already exactly what [Rspamd] url defaults to in settings.ini. Just set
# [Rspamd] enabled = true after first boot (see docker-deploy/README.md) and restart.
#
# Build from the repo root:
# docker build -f docker-deploy/Dockerfile.rspamd -t mailgoserver-rspamd .
FROM golang:1.26-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mailgoserver .
FROM debian:bookworm-slim
# rspamd is installed from its own APT repo (rspamd.com), not Debian's bundled
# package, which tends to lag several releases behind — this is genuinely the latest
# stable release, matching what was asked for.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates tzdata curl gnupg lsb-release \
&& mkdir -p /usr/share/keyrings \
&& curl -fsSL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/rspamd.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/rspamd.gpg] https://rspamd.com/apt-stable/ $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/rspamd.list \
&& apt-get update && apt-get install -y --no-install-recommends rspamd \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver
COPY docker-deploy/entrypoint-rspamd.sh /usr/local/bin/entrypoint-rspamd.sh
RUN chmod +x /usr/local/bin/entrypoint-rspamd.sh
# /app/data: same as the standalone image (settings.ini, DB, mailstore, keys, certs).
# /var/lib/rspamd: rspamd's own Bayes/fuzzy-hash storage, kept persistent separately so
# rebuilding the image doesn't reset spam-learning state.
WORKDIR /app/data
VOLUME ["/app/data", "/var/lib/rspamd"]
EXPOSE 25 465 143 993 5000 5001
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -fs http://127.0.0.1:5000/health || exit 1
ENTRYPOINT ["/usr/local/bin/entrypoint-rspamd.sh"]
+98
View File
@@ -0,0 +1,98 @@
# Docker deployment
Two independent images, both built from the same source tree:
- **`Dockerfile`** — mailgoserver only.
- **`Dockerfile.rspamd`** — mailgoserver + the latest [rspamd](https://rspamd.com) in the
*same* container, already wired together (see below). Use this one if you want
stronger spam filtering than the built-in heuristic score alone.
`docker-compose.yml` defines both as Compose **profiles** so a plain `docker compose up`
can't accidentally start both at once:
```bash
cd docker-deploy
# mailserver only
docker compose --profile standalone up -d --build
# mailserver + rspamd, bundled
docker compose --profile with-rspamd up -d --build
```
Either way, the app itself now binds the real standard mail ports by default — 25
(SMTP), 465 (direct-TLS SMTP), 143 (IMAP), 993 (direct-TLS IMAP) — and the admin/webmail
UI on its usual non-privileged 5000/5001 (HTTP/HTTPS); put your own reverse proxy or a
`80:5000`/`443:5001` port mapping in front if you want those on 80/443 too. Binding the
low mail ports needs no extra capability here since the container runs as root. Copy
`.env.example` to `.env` in this folder to change any host-side port — useful if
something else on the host already owns 25/143/etc., or if you want to run both
profiles side by side.
## What happens on first boot
There's no baked-in config. On first start, the binary generates a fresh
`settings.ini` with defaults (mirroring `internal/config/config.go`), a self-signed TLS
certificate, DKIM/mailstore master keys, and an empty SQLite database — all inside the
`/app/data` volume, so it survives container restarts/rebuilds. The web UI seeds one
admin account: username `admin`, password `Password123!`, and forces an immediate
username + password change on first login — see the main [README](../README.md) for
the full first-login walkthrough.
**Before using this for real mail**, exec into the container (or edit the volume from
the host) and update `settings.ini`:
```bash
docker exec -it mailgoserver sh -c 'vi /app/data/settings.ini'
docker restart mailgoserver
```
At minimum, set `[Server] HOSTNAME` / `helo_hostname` to your real mail domain, and if
you'll use passkeys, `[Auth] rp_id` / `rp_origin` to match the exact domain the admin
dashboard is reached at (`rp_id` can't be `localhost` once you're on a real domain —
see the main README's WebAuthn note). There's no environment-variable override
mechanism — `settings.ini` in the volume is the one source of config truth.
## Enabling rspamd (the `with-rspamd` profile)
The bundled rspamd's default config already listens on `127.0.0.1:11333` for scanning
requests — exactly what `[Rspamd] url` defaults to in `settings.ini`, and since both
processes share the container's network namespace, no networking setup is needed. All
that's left is turning it on:
```ini
[Rspamd]
enabled = true
url = http://127.0.0.1:11333
reject_score = 15
```
...then restart the container. The built-in heuristic spam score
(`internal/mailstore/spam.go`) always runs regardless of this setting — rspamd is
additive, not a replacement, and if it's ever unreachable, mail still flows on the
heuristic score alone (rspamd errors are logged, never fatal to delivery).
This bundle intentionally skips Redis — rspamd runs fine without it for SPF/DKIM/RBL/
regexp-based scoring, but Bayes learning and greylisting need it. Add a `redis` service
to `docker-compose.yml` and point rspamd's `redis.conf` at it if you need those.
## Persistence
| Volume | What's in it |
|---|---|
| `mailserver-data` / `mailserver-rspamd-data` | `settings.ini`, the SQLite DB, encrypted mailbox storage, DKIM/mailstore master keys, TLS certs, the CSRF app secret — everything mailgoserver itself owns. |
| `rspamd-data` (rspamd profile only) | rspamd's own Bayes/fuzzy-hash storage, so spam-learning state survives image rebuilds. |
Back up the `*-data` volume like you would the equivalent bare-metal `server_data/`
directory — losing the mailstore master key makes all stored mail unrecoverable, same
as a non-Docker install.
## Logs / health
```bash
docker compose --profile standalone logs -f # or --profile with-rspamd
docker inspect --format '{{.State.Health.Status}}' mailgoserver
```
Both images expose `GET /health` (used by the container `HEALTHCHECK`), matching the
JSON the admin dashboard's own health check reads.
+51
View File
@@ -0,0 +1,51 @@
# Two independent setups, selected with --profile so they don't both start by accident:
#
# docker compose --profile standalone up -d --build # mailserver only
# docker compose --profile with-rspamd up -d --build # mailserver + rspamd, same container
#
# Both default to the standard mail ports on the host (25/465/143/993) — the app itself
# now binds those directly, no port remapping needed — plus the app's own non-privileged
# web ports (5000/5001; put a reverse proxy or your own 80/443 mapping in front of those
# if you want the dashboard on standard web ports too). Only run one profile at a time
# unless you've overridden the host ports for one of them (see .env.example) — they'd
# otherwise both try to bind the same host ports.
services:
mailserver:
build:
context: ..
dockerfile: docker-deploy/Dockerfile
container_name: mailgoserver
restart: unless-stopped
profiles: ["standalone"]
ports:
- "${SMTP_PORT:-25}:25"
- "${SMTP_TLS_PORT:-465}:465"
- "${IMAP_PORT:-143}:143"
- "${IMAP_TLS_PORT:-993}:993"
- "${WEB_HTTP_PORT:-5000}:5000"
- "${WEB_HTTPS_PORT:-5001}:5001"
volumes:
- mailserver-data:/app/data
mailserver-rspamd:
build:
context: ..
dockerfile: docker-deploy/Dockerfile.rspamd
container_name: mailgoserver-rspamd
restart: unless-stopped
profiles: ["with-rspamd"]
ports:
- "${SMTP_PORT:-25}:25"
- "${SMTP_TLS_PORT:-465}:465"
- "${IMAP_PORT:-143}:143"
- "${IMAP_TLS_PORT:-993}:993"
- "${WEB_HTTP_PORT:-5000}:5000"
- "${WEB_HTTPS_PORT:-5001}:5001"
volumes:
- mailserver-rspamd-data:/app/data
- rspamd-data:/var/lib/rspamd
volumes:
mailserver-data:
mailserver-rspamd-data:
rspamd-data:
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
# Starts rspamd in the background, then hands off to mailgoserver as the container's
# main (foreground) process.
#
# ponytail: no supervisor / restart-on-crash for rspamd here — if it dies mid-run, mail
# keeps flowing without the extra scoring rather than the container crashing (the
# built-in heuristic score in internal/mailstore/spam.go always runs regardless, and
# CheckRspamd swallows connection errors rather than rejecting mail over them — see
# internal/mailstore/rspamd.go). Add s6-overlay or supervisord if unattended rspamd
# uptime matters more than that.
rspamd -f &
# Give rspamd's normal worker a moment to bind :11333 before the first message can
# possibly arrive — purely cosmetic, mailgoserver degrades gracefully either way.
sleep 1
exec mailgoserver --host 0.0.0.0 "$@"
+7 -5
View File
@@ -25,9 +25,11 @@ var defaults = []struct {
{"Server", []defaultKV{ {"Server", []defaultKV{
{"", "", "Server configuration for SMTP ports and hostname"}, {"", "", "Server configuration for SMTP ports and hostname"},
{"", "", "Plain SMTP port for internal/whitelisted IPs"}, {"", "", "Plain SMTP port for internal/whitelisted IPs"},
{"SMTP_PORT", "4025", ""}, {"", "", "(standard port 25 — the process needs CAP_NET_BIND_SERVICE or root to bind"},
{"", "", "TLS SMTP port for authenticated users"}, {"", "", "it directly; not an issue in the Docker deployment, which runs as root)"},
{"SMTP_TLS_PORT", "40465", ""}, {"SMTP_PORT", "25", ""},
{"", "", "Direct/implicit-TLS SMTP port for authenticated users (like SMTPS, not STARTTLS)"},
{"SMTP_TLS_PORT", "465", ""},
{"", "", "Server hostname for HELO/EHLO identification"}, {"", "", "Server hostname for HELO/EHLO identification"},
{"HOSTNAME", "mail.example.com", ""}, {"HOSTNAME", "mail.example.com", ""},
{"", "", "Override HELO hostname"}, {"", "", "Override HELO hostname"},
@@ -112,9 +114,9 @@ var defaults = []struct {
{"IMAP", []defaultKV{ {"IMAP", []defaultKV{
{"", "", "IMAP server configuration for mailbox retrieval (Thunderbird, etc.)"}, {"", "", "IMAP server configuration for mailbox retrieval (Thunderbird, etc.)"},
{"", "", "Plain IMAP port (STARTTLS not offered, matching the SMTP plain-port design)"}, {"", "", "Plain IMAP port (STARTTLS not offered, matching the SMTP plain-port design)"},
{"IMAP_PORT", "1143", ""}, {"IMAP_PORT", "143", ""},
{"", "", "Implicit-TLS IMAP port (IMAPS)"}, {"", "", "Implicit-TLS IMAP port (IMAPS)"},
{"IMAP_TLS_PORT", "1993", ""}, {"IMAP_TLS_PORT", "993", ""},
}}, }},
{"Mailstore", []defaultKV{ {"Mailstore", []defaultKV{
{"", "", "Local mailbox storage configuration"}, {"", "", "Local mailbox storage configuration"},
+4 -4
View File
@@ -17,8 +17,8 @@ func TestGenerateAndLoadRoundTrip(t *testing.T) {
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
t.Fatalf("settings.ini was not written: %v", err) t.Fatalf("settings.ini was not written: %v", err)
} }
if got := cfg.Section("Server").Key("SMTP_PORT").String(); got != "4025" { if got := cfg.Section("Server").Key("SMTP_PORT").String(); got != "25" {
t.Errorf("SMTP_PORT = %q, want 4025", got) t.Errorf("SMTP_PORT = %q, want 25", got)
} }
if got := cfg.Section("Attachments").Key("attachments_path").String(); got == "" { if got := cfg.Section("Attachments").Key("attachments_path").String(); got == "" {
t.Error("Attachments.attachments_path default is missing (the approved bug fix)") t.Error("Attachments.attachments_path default is missing (the approved bug fix)")
@@ -29,8 +29,8 @@ func TestGenerateAndLoadRoundTrip(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Load (existing): %v", err) t.Fatalf("Load (existing): %v", err)
} }
if got := cfg2.Section("Server").Key("SMTP_PORT").String(); got != "4025" { if got := cfg2.Section("Server").Key("SMTP_PORT").String(); got != "25" {
t.Errorf("second Load: SMTP_PORT = %q, want 4025", got) t.Errorf("second Load: SMTP_PORT = %q, want 25", got)
} }
} }
+4 -4
View File
@@ -138,8 +138,8 @@ func main() {
var smtpRunning atomic.Bool var smtpRunning atomic.Bool
runSMTP := func() { runSMTP := func() {
smtpPort := cfg.Section("Server").Key("SMTP_PORT").MustInt(4025) smtpPort := cfg.Section("Server").Key("SMTP_PORT").MustInt(25)
smtpTLSPort := cfg.Section("Server").Key("SMTP_TLS_PORT").MustInt(40465) smtpTLSPort := cfg.Section("Server").Key("SMTP_TLS_PORT").MustInt(465)
banner := smtpserver.ResolveBanner(cfg, heloHostname) banner := smtpserver.ResolveBanner(cfg, heloHostname)
// Bind IPv4-only ("0.0.0.0"), matching both listeners in server_runner.py // Bind IPv4-only ("0.0.0.0"), matching both listeners in server_runner.py
@@ -175,8 +175,8 @@ func main() {
} }
runIMAP := func() { runIMAP := func() {
imapPort := cfg.Section("IMAP").Key("IMAP_PORT").MustInt(1143) imapPort := cfg.Section("IMAP").Key("IMAP_PORT").MustInt(143)
imapTLSPort := cfg.Section("IMAP").Key("IMAP_TLS_PORT").MustInt(1993) imapTLSPort := cfg.Section("IMAP").Key("IMAP_TLS_PORT").MustInt(993)
plainServer := imapserver.NewPlainServer(imapBackend) plainServer := imapserver.NewPlainServer(imapBackend)
tlsServer := imapserver.NewTLSServer(imapBackend, tlsConfig) tlsServer := imapserver.NewTLSServer(imapBackend, tlsConfig)