
🐄🚀 Mailcow + Clickhouse + Redash: Keep Track of Your Email Server!
Table of Contents
Intro
mailcow makes self-hosting emails super easy and convenient. But honestly, something’s missing for me. Once everything is set up, you quickly realize that you don’t really have an overview of your email server. How many emails are coming in? How many are going out? And even more important: is spam possibly being sent through my server? Without such insights, the whole thing feels a bit like flying blind.
Thankfully, there’s Open Source! With the already integrated Rspamd spam filter, we can push all kinds of statistics into a Clickhouse database and use Redash to create a sleek dashboard.
We integrate all of this directly into the mailcow stack using a docker-compose.override.yml
🚀
Deploy Redash and Clickhouse
- Navigate to the mailcow directory and create a
docker-compose.override.yml
cd /opt/mailcow-dockerzied
nano docker-compose.override.yml
## docker-compose.override.yml
services:
redash:
image: redash/redash:latest
restart: always
command: server
environment:
REDASH_DATABASE_URL: "${REDASH_DATABASE_URL}"
REDASH_REDIS_URL: "${REDASH_REDIS_URL}"
REDASH_COOKIE_SECRET: "${REDASH_COOKIE_SECRET}"
REDASH_SECRET_KEY: "${REDASH_SECRET_KEY}"
depends_on:
- postgres-redash
- redis-redash
volumes:
- redash_redash_data:/app
networks:
mailcow-network:
scheduler-redash:
image: redash/redash:latest
restart: always
command: scheduler
environment:
REDASH_DATABASE_URL: "${REDASH_DATABASE_URL}"
REDASH_REDIS_URL: "${REDASH_REDIS_URL}"
REDASH_COOKIE_SECRET: "${REDASH_COOKIE_SECRET}"
REDASH_SECRET_KEY: "${REDASH_SECRET_KEY}"
depends_on:
- redash
networks:
mailcow-network:
scheduled-worker-redash:
image: redash/redash:latest
restart: always
command: worker
environment:
REDASH_DATABASE_URL: "${REDASH_DATABASE_URL}"
REDASH_REDIS_URL: "${REDASH_REDIS_URL}"
REDASH_COOKIE_SECRET: "${REDASH_COOKIE_SECRET}"
REDASH_SECRET_KEY: "${REDASH_SECRET_KEY}"
QUEUES: "scheduled_queries,schemas"
WORKERS_COUNT: 1
depends_on:
- redash
networks:
mailcow-network:
adhoc-worker-redash:
image: redash/redash:latest
restart: always
command: worker
environment:
REDASH_DATABASE_URL: "${REDASH_DATABASE_URL}"
REDASH_REDIS_URL: "${REDASH_REDIS_URL}"
REDASH_COOKIE_SECRET: "${REDASH_COOKIE_SECRET}"
REDASH_SECRET_KEY: "${REDASH_SECRET_KEY}"
QUEUES: "queries"
WORKERS_COUNT: 2
depends_on:
- redash
networks:
mailcow-network:
postgres-redash:
image: postgres:12-alpine
restart: always
environment:
POSTGRES_USER: "${POSTGRES_USER}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: "${POSTGRES_DB}"
volumes:
- postgres_redash_data:/var/lib/postgresql/data
networks:
mailcow-network:
redis-redash:
image: redis:7-alpine
restart: always
volumes:
- redis_redash_data:/data/
environment:
- TZ=${TZ}
sysctls:
- net.core.somaxconn=4096
networks:
mailcow-network:
clickhouse-redash:
image: yandex/clickhouse-server:latest
restart: always
volumes:
- clickhouse_redash_data:/var/lib/clickhouse
networks:
mailcow-network:
nginx-mailcow:
ports:
- "${HTTPS_BIND:-}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
- "${HTTP_BIND:-}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
- "${HTTPS_REDASH_BIND:-}:${HTTPS_REDASH_PORT:-443}:${HTTPS_REDASH_PORT:-443}"
- "${HTTP_REDASH_BIND:-}:${HTTP_REDASH_PORT:-80}:${HTTP_REDASH_PORT:-80}"
volumes:
redash_redash_data:
postgres_redash_data:
redis_redash_data:
clickhouse_redash_data:
- Next, extend the
mailcow.conf
file by adding the following variables at the end (Postgres password and secrets need to be generated manually):
nano mailcow.conf
## mailcow.conf
# Clickhouse Redash
REDASH_DATABASE_URL=postgresql://redash:<POSTGRES_SQL_PASSWORD>@postgres-clickhouse/redash
REDASH_REDIS_URL=redis://redis-clickhouse:6379/0
REDASH_COOKIE_SECRET=<RANDOM_COOKIE_SECRET>
REDASH_SECRET_KEY=<RANDOM_KEY_SECRET>
HTTP_REDASH_BIND=
HTTPS_REDASH_BIND=
HTTP_REDASH_PORT=8008
HTTPS_REDASH_PORT=8009
# Clickhouse PostgreSQL
POSTGRES_USER=redash
POSTGRES_PASSWORD=<POSTGRES_SQL_PASSWORD>
POSTGRES_DB=redash
# Clickhouse Redis
REDIS_URL=redis://redis-clickhouse:6379/0
- Next, we make Redash accessible through the mailcow Nginx reverse proxy. To do this, create a
redash.conf
file in the Nginx config folder.
nano data/conf/nginx/redash.conf
## data/conf/nginx/redash.conf
server {
listen 8008;
listen 8009 ssl;
listen [::]:8008;
listen [::]:8009 ssl;
http2 on;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_ecdh_curve X25519:X448:secp384r1:secp256k1;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
include /etc/nginx/conf.d/server_name.active;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://redash:5000;
proxy_redirect off;
}
}
- !!! IMPORTANT !!! After that, initialize the Redash database in PostgreSQL and restart your mailcow stack.
docker compose run --rm redash create_db
docker compose down && docker compose up -d
Configure Rspamd
To enable Rspamd to store statistics in Clickhouse, we need to create an appropriate clickhouse.conf
.
For more information on configuring the module, visit: Rspamd Clickhouse Module Documentation
Note: I set limit = 1
this low only for testing purposes.
nano data/conf/rspamd/local.d/clickhouse.conf
## data/conf/rspamd/local.d/clickhouse.conf
# Push update when 1 (for testing!) records are collected (1000 if unset) (until 2.1, see limits section below for >2.1)
limit = 1;
# IP:port of Clickhouse server ("localhost:8123" if unset)
server = "clickhouse-redash:8123"; # clickhouse-redash is the containername
# Timeout to wait for response (5 seconds if unset)
timeout = 5;
# How many bits of sending IP to mask in logs for IPv4 (19 if unset)
ipmask = 19;
# How many bits of sending IP to mask in logs for IPv6 (48 if unset)
ipmask6 = 48;
# Record URL paths? (default false)
full_urls = false;
# This parameter points to a map of domain names
# If a message has a domain in this map in From: header and DKIM signature,
# record general metadata in a table named after the domain
#from_tables = "/etc/rspamd/clickhouse_from.map";
# store digest/hash of email (default false)
enable_digest = false;
# store Symbols.Names, Symbols.Scores, Symbols.Options, Groups.Names and Groups.Scores (default false)
enable_symbols = false;
# Subject related (from 1.9)
insert_subject = false;
# Privacy is off
subject_privacy = false;
# Default hash-algorithm to obfuscate subject
subject_privacy_alg = 'blake2';
# Prefix to show it's obfuscated
subject_privacy_prefix = 'obf';
# Cut the length of the hash
subject_privacy_length = 16;
# Store data for local scanes
allow_local = true;
# Other options
#database = 'default';
#use_https = false;
# Transport compression
#use_gzip = true;
# Basic auth
#user = null;
#password = null;
# Disable SSL verification
#no_ssl_verify = false,
# This section configures how long the data will be stored in ClickHouse
#retention {
# # disabled by default
# enable = true;
# # drop | detach, please refer to ClickHouse docs for details
# # https://clickhouse.com/docs/en/sql-reference/statements/alter/partition
# method = "drop";
# # how many month the data should be kept in ClickHouse
# period_months = 3;
# # how often run the cleanup process
# run_every = 7d;
#}
# This section defines how often Rspamd will send data to Clickhouse (from 2.1)
#limits {
# max_rows = 1000; # How many rows are allowed (0 for disable this)
# max_memory = 50mb; # How many memory should be occupied before sending collection
# max_interval = 60s; # Maximum collection interval
#}
After that, you need to restart the Rspamd container with docker compose restart rspamd-mailcow
.
Create a Dashboard
We can now access Redash via HTTPS on port 8009 (https://mailcow.tld:8009).
First, create an admin account.
Once logged in as admin, follow these steps:
-
Add a Data Source
Go to https://mailcow.tld:8009/data_sources and add a new Data Source of type Clickhouse.
-
Create a Query
Now, create your first query at https://mailcow.tld:8009/queries/new, which will show us how many emails we have received on that day.
SELECT
count() AS c
FROM {{database}}.rspamd
WHERE (Date = today()) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
Next, add a visualization below. Click on + New Visualization
and configure it as follows:
-
Create a Dashboard
Next, create a new dashboard and then click onAdd Widget
at the bottom right.
You should now be able to see and select your query.
-
Done 🥳
Now, send some test emails back and forth, and after a while, you should be able to see how many emails we’ve received.
Example Queries
Recieved Emails Today
SELECT
count() AS c
FROM {{database}}.rspamd
WHERE (Date = today()) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
Recieved Spam today
SELECT
count() AS c
FROM {{database}}.rspamd
WHERE (Date = today()) AND AuthUser == '' AND ((Action = 'reject') OR (Action = 'add header'))
Sent Emails Today
SELECT
count() AS c
FROM {{database}}.rspamd
WHERE (Date = today()) AND AuthUser != ''
Top Incoming Domains for Spam and Ham
SELECT
From,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND ((Action = 'reject') OR (Action = 'add header')) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
GROUP BY From
ORDER BY c DESC
LIMIT {{limit}}
Top Incoming IP
SELECT
IP,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
GROUP BY IP
ORDER BY c DESC
LIMIT {{limit}}
Top Incoming Senders
SELECT
concat(FromUser, '@', From) AS user,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
GROUP BY user
ORDER BY c DESC
LIMIT {{limit}}
Top Incoming Sender Domains
SELECT
From AS domain,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser == '' AND From != '' AND MimeFrom != 'localhost'
GROUP BY domain
ORDER BY c DESC
LIMIT {{limit}}
Top Outgoing Senders
SELECT
AuthUser AS user,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser != ''
GROUP BY user
ORDER BY c DESC
LIMIT {{limit}}
Top Outgoing Sender Domains
SELECT
From AS domain,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser != ''
GROUP BY domain
ORDER BY c DESC
LIMIT {{limit}}
Top Outgoing IP
SELECT
IP,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND AuthUser != ''
GROUP BY IP
ORDER BY c DESC
LIMIT {{limit}}
Top Outgoing Domains for Spam and Ham
SELECT
From,
count() AS c
FROM {{database}}.rspamd
WHERE (Date >= today() - INTERVAL {{last_days}} DAY) AND ((Action = 'reject') OR (Action = 'add header')) AND AuthUser != ''
GROUP BY From
ORDER BY c DESC
LIMIT {{limit}}