-
Mauro, congrats on the v0.7.0 release! Besides the awesome UI, having all the config in one file is highly appreciated 😄 I'm struggling to get Managesieve working with Roundcube. I've set up Stalwart-mail v0.7.0 in Docker with Postgres as the default store, proxied behind Traefik with Proxy Protocol 2. TLS certs have been extracted from Traefik's ACME store and placed in the Stalwart container. Implicit TLS for port 465 and 993 works as expected from Roundcube and other mail clients. For sieve, I can successfully connect via TLS with openssl from another server but in Roundcube, whenever I go to the Filters tab, it times out with an error stating unable to connect. This same Roundcube instance can successfully connect to another mailserver with sieve via TLS. So that tells me that Roundcube's config is fine and it's something to do with my Stalwart config. Do you have any ideas or suggestions on how to resolve this? And with logging set to trace, I don't see any sieve-related logs. Relevant config: Stalwart-mail docker-compose.ymlversion: '3.7'
networks:
ingress:
external: true
default:
internal: true
name: stalwart
services:
stalwart:
container_name: ${SERVICE_NAME}-mail
image: stalwartlabs/mail-server:${SERVICE_VERSION} # arm and x86
depends_on:
db:
condition: service_healthy
env_file: .env
networks:
ingress:
ipv4_address: '172.18.0.20'
default:
security_opt: [no-new-privileges:true]
restart: unless-stopped
# healthcheck:
labels:
- traefik.enable=true
# admin ui
- traefik.http.routers.stalwart.rule=Host(`${DOMAIN}`)
- traefik.http.routers.stalwart.entrypoints=websecure
- traefik.http.routers.stalwart.tls.certresolver=le
- traefik.http.routers.stalwart.service=stalwart
- traefik.http.services.stalwart.loadbalancer.server.port=8080
# jmap
- traefik.tcp.routers.jmap.rule=HostSNI(`*`)
- traefik.tcp.routers.jmap.entrypoints=websecure
- traefik.tcp.routers.jmap.tls.passthrough=true
- traefik.tcp.routers.jmap.service=jmap
- traefik.tcp.services.jmap.loadbalancer.server.port=443
- traefik.tcp.services.jmap.loadbalancer.proxyProtocol.version=2
# smtp
- traefik.tcp.routers.smtp.rule=HostSNI(`*`)
- traefik.tcp.routers.smtp.entrypoints=smtp
- traefik.tcp.routers.smtp.service=smtp
- traefik.tcp.services.smtp.loadbalancer.server.port=25
- traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=2
# esmtp
- traefik.tcp.routers.esmtp.rule=HostSNI(`*`)
- traefik.tcp.routers.esmtp.entrypoints=esmtp
- traefik.tcp.routers.esmtp.tls.passthrough=true
- traefik.tcp.routers.esmtp.service=esmtp
- traefik.tcp.services.esmtp.loadbalancer.server.port=465
- traefik.tcp.services.esmtp.loadbalancer.proxyProtocol.version=2
# imap
- traefik.tcp.routers.imap.rule=HostSNI(`*`)
- traefik.tcp.routers.imap.entrypoints=imap
- traefik.tcp.routers.imap.tls.passthrough=true
- traefik.tcp.routers.imap.service=imap
- traefik.tcp.services.imap.loadbalancer.server.port=993
- traefik.tcp.services.imap.loadbalancer.proxyProtocol.version=2
# sieve
- traefik.tcp.routers.sieve.rule=HostSNI(`*`)
- traefik.tcp.routers.sieve.entrypoints=sieve
- traefik.tcp.routers.sieve.tls.passthrough=true
- traefik.tcp.routers.sieve.service=sieve
- traefik.tcp.services.sieve.loadbalancer.server.port=4190
- traefik.tcp.services.sieve.loadbalancer.proxyProtocol.version=2
volumes:
- ./data:/opt/stalwart-mail
logging:
driver: 'json-file'
options:
max-size: '200k'
max-file: '10'
# ports:
# - '25:25'
# - '443:443'
# - '465:465'
# # - "587:587" # not using
# - '993:993'
# - '4190:4190'
# - '8080:8080' # admin ui
db:
container_name: ${SERVICE_NAME}-db
image: postgres:${DB_VERSION}
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- TZ
networks: [default]
restart: unless-stopped
healthcheck:
test: ['CMD', 'pg_isready', '-q', '-d', '${DB_NAME}', '-U', '${DB_USER}']
timeout: 45s
interval: 10s
retries: 10
volumes: [./db:/var/lib/postgresql/data]
Stalwart-mail etc/config.tomlauthentication.fallback-admin.secret = "SECRET"
authentication.fallback-admin.user = "admin"
certificate.traefik.cert = "%{file:/opt/stalwart-mail/certs/mail.domain.tld.crt}%"
certificate.traefik.private-key = "%{file:/opt/stalwart-mail/certs/mail.domain.tld.key}%"
certificate.traefik.default = true
cluster.node-id = 1
directory.postgres.cache.entries = 500
directory.postgres.cache.ttl.negative = "10m"
directory.postgres.cache.ttl.positive = "1h"
directory.postgres.columns.class = "type"
directory.postgres.columns.email = "address"
directory.postgres.columns.description = "description"
directory.postgres.columns.name = "name"
directory.postgres.columns.quota = "quota"
directory.postgres.columns.secret = "secret"
directory.postgres.store = "postgres"
directory.postgres.type = "sql"
lookup.default.hostname = "mail.domain.tld"
server.http.permissive-cors = false
server.http.url = "protocol + '://' + key_get('default', 'hostname') + ':' + local_port"
server.http.use-x-forwarded = false
server.listener.http.bind = "[::]:8080"
server.listener.http.protocol = "http"
server.listener.https.bind = "[::]:443"
server.listener.https.protocol = "http"
server.listener.https.tls.implicit = true
server.listener.imaptls.bind = "[::]:993"
server.listener.imaptls.protocol = "imap"
server.listener.imaptls.tls.implicit = true
server.listener.sieve.bind = "[::]:4190"
server.listener.sieve.protocol = "managesieve"
server.listener.sieve.tls.implicit = true
server.listener.smtp.bind = "[::]:25"
server.listener.smtp.protocol = "smtp"
server.listener.submission.bind = "[::]:587"
server.listener.submission.protocol = "smtp"
server.listener.submissions.bind = "[::]:465"
server.listener.submissions.protocol = "smtp"
server.listener.submissions.tls.implicit = true
server.max-connections = 8192
server.proxy.trusted-networks = "172.18.0.0/16"
server.socket.backlog = 1024
server.socket.nodelay = true
server.socket.reuse-addr = true
server.socket.reuse-port = true
server.tls.certificate = "traefik"
server.tls.enable = true
storage.blob = "r2"
storage.data = "postgres"
storage.directory = "postgres"
storage.encryption.enable = true
storage.encryption.append = false
storage.fts = "postgres"
storage.lookup = "postgres"
store.postgres.compression = "lz4"
store.postgres.database = "stalwart"
store.postgres.host = "db"
store.postgres.password = "SECRET"
store.postgres.pool.max-connections = 10
store.postgres.port = 5432
store.postgres.purge.frequency = "0 3 *"
store.postgres.query.domains = "SELECT 1 FROM emails WHERE address LIKE '%@' || $1 LIMIT 1"
store.postgres.query.emails = "SELECT address FROM emails WHERE name = $1 AND type != 'list' ORDER BY type DESC, address ASC"
store.postgres.query.expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = $1 AND l.type = 'list' ORDER BY p.address LIMIT 50"
store.postgres.query.members = "SELECT member_of FROM group_members WHERE name = $1"
store.postgres.query.name = "SELECT name, type, secret, description, quota FROM accounts WHERE name = $1 AND active = true"
store.postgres.query.recipients = "SELECT name FROM emails WHERE address = $1 ORDER BY name ASC"
store.postgres.query.verify = "SELECT address FROM emails WHERE address LIKE '%' || $1 || '%' AND type = 'primary' ORDER BY address LIMIT 5"
store.postgres.timeout = "15s"
store.postgres.tls.allow-invalid-certs = false
store.postgres.tls.enable = false
store.postgres.type = "postgresql"
store.postgres.user = "stalwart"
store.r2.access-key = "SECRET"
store.r2.bucket = "stalwart"
store.r2.endpoint = "https://SECRET.r2.cloudflarestorage.com"
store.r2.purge.frequency = "0 3 *"
store.r2.region = "auto"
store.r2.secret-key = "SECRET"
store.r2.timeout = "15s"
store.r2.type = "s3"
tracer.stdout.ansi = true
tracer.stdout.enable = true
tracer.stdout.level = "debug"
tracer.stdout.type = "stdout"
Traefik docker-compose.ymlversion: '3.7'
networks:
# docker network create ingress --subnet "172.18.0.0/24" --gateway "172.18.0.1"
ingress:
external: true
default:
internal: true
name: traefik
services:
traefik:
container_name: traefik
image: traefik:${TRAEFIK_VERSION}
depends_on: [dockerproxy, watchtower]
networks:
ingress:
ipv4_address: '172.18.0.10'
default:
security_opt: [no-new-privileges:true]
ports:
- '80:80'
- '443:443'
# Ports for MailServer
- '25:25' # SMTP receiving, explicit TLS
- '465:465' # ESMTP submission, implicit TLS
- '993:993' # IMAP4 secure, implicit TLS
- '4190:4190' # sieve
restart: always
command:
- --log.level=DEBUG # DEBUG, PANIC, FATAL, ERROR, WARN, INFO
- --global.checknewversion=false
- --global.sendAnonymousUsage=false
# Activate dashboard
- --api=true
# Activate /ping healthcheck endpoint
- --ping=true
- --ping.manualRouting=true
- --ping.entrypoint=websecure
# Activate docker provider, but do not expose every container to traefik
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.endpoint=tcp://dockerproxy:2375
- --providers.docker.network=ingress
# HTTPS entrypoints and redirect
- --entrypoints.webinsecure.address=:80
- --entrypoints.webinsecure.http.redirections.entrypoint.to=websecure
- --entrypoints.webinsecure.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
# TCP entrypoints for MailServer
- --entrypoints.smtp.address=:25
- --entrypoints.esmtp.address=:465
- --entrypoints.imap.address=:993
- --entrypoints.sieve.address=:4190
# DNS Challenge with Cloudflare
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.le.acme.dnschallenge.resolvers=1.1.1.1:53,9.9.9.9:53
- --certificatesresolvers.le.acme.email=${LETS_ENCRYPT_EMAIL}
- --certificatesresolvers.le.acme.storage=/certs/acme.json
# Let's Encrypt Staging Server, start with staging then switch to prod server
#- --certificatesResolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
# Let's Encrypt Production Server
- --certificatesResolvers.le.acme.caServer=https://acme-v02.api.letsencrypt.org/directory
environment:
- CLOUDFLARE_DNS_API_TOKEN
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
# Traefik Dashboard available at $DASHBOARD_DOMAIN/dashboard/ (trailing slash required)
- traefik.http.routers.dashboard.rule=Host(`${DASHBOARD_DOMAIN}`)
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls.certresolver=le
- traefik.http.routers.dashboard.service=api@internal
# - traefik.http.routers.dashboard.middlewares=secure-headers@file
- traefik.http.routers.dashboard.middlewares=dashboard-auth
- traefik.http.middlewares.dashboard-auth.basicauth.usersfile=/.env.users
# Routers for /ping healthcheck endpoint
- traefik.http.routers.ping.rule=Host(`${DASHBOARD_DOMAIN}`) && Path(`/ping`)
- traefik.http.routers.ping.entrypoints=websecure
- traefik.http.routers.ping.tls.certresolver=le
- traefik.http.routers.ping.service=ping@internal
volumes:
- ./certs:/certs
- ./.env.users:/.env.users:ro # add user:hashed-password to .env.users
dockerproxy:
container_name: traefik-dockerproxy
image: ghcr.io/tecnativa/docker-socket-proxy:${DOCKER_SOCKET_PROXY_VERSION}
depends_on: [watchtower]
environment:
CONTAINERS: 1
networks: [default]
ports: [2375]
restart: always
labels:
- com.centurylinklabs.watchtower.enable=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
watchtower:
container_name: traefik-watchtower
image: containrrr/watchtower:${WATCHTOWER_VERSION}
command: --label-enable --cleanup --interval 86400
network_mode: none
restart: always
labels:
- com.centurylinklabs.watchtower.enable=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Roundcube's customconfig.php<?php
$config['product_name'] = 'My Webmail';
$config['plugins'] = ['managesieve'];
// %h - user's IMAP hostname
$config['imap_host'] = ['ssl://mail.domain.tld'];
$config['managesieve_host'] = 'tls://%h'; # prefex tls:// in v1.6.6 Successful connection with openssl to sieve with TLS from another server:
Traefik's logs showing a successful connection to Stalwart's 4190 port:
Roundcube logs:
Stalwart logs (no sieve logs):
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
It might be that Roundcube is expecting a plain-text ManageSieve connection. You can try disabling implicit TLS on Stalwart and then try again. |
Beta Was this translation helpful? Give feedback.
It might be that Roundcube is expecting a plain-text ManageSieve connection. You can try disabling implicit TLS on Stalwart and then try again.
Make sure you enable
STARTTLS
on Rouncube otherwise you won't be able to authenticate. It is possible to allow plain-text auth in Stalwart but it is not recommended.