Skip to content

Deploying Exim

Introduction

Exim is a highly flexible, open-source message transfer agent (MTA) originally developed at the University of Cambridge. Written in C, Exim is renowned for its powerful configuration system that provides exceptional control over mail routing, extensive filtering capabilities, and sophisticated access control mechanisms. Unlike many MTAs that follow rigid configuration patterns, Exim offers a rule-based approach that adapts to virtually any mail infrastructure requirement.

Exim handles billions of emails daily across the internet and powers some of the largest mail systems worldwide. Its comprehensive feature set includes support for virtual domains, database-driven routing, content scanning integration, DKIM signing, SPF verification, and detailed mail queue management. Whether you’re building a departmental mail server, hosting email for multiple domains, or integrating email capabilities into your application, Exim provides the flexibility and reliability you need.

Deploying Exim on Klutch.sh gives you a production-ready mail transfer agent with persistent storage for mail queues and logs, secure configuration management through environment variables, and the ability to scale compute resources as your email volume grows. This guide walks you through creating a containerized Exim deployment, configuring essential mail routing, implementing security best practices, and integrating with external authentication systems.

Why Deploy Exim on Klutch.sh?

  • Automated Docker Detection: Klutch.sh automatically detects your Dockerfile and builds your Exim container without manual configuration
  • Persistent Mail Queues: Ensure mail queues and spool files survive container restarts with persistent volume support
  • Flexible Configuration: Manage Exim settings through environment variables with secure secret storage for credentials
  • TCP Traffic Support: Native support for SMTP traffic routing on standard ports with external access on port 8000
  • Resource Scaling: Adjust CPU and memory allocation based on mail volume and processing requirements
  • Secure Deployment: Built-in network isolation and encrypted environment variable storage for sensitive mail server credentials
  • Production-Ready Infrastructure: Deploy with confidence using containerized mail infrastructure with automatic health monitoring
  • GitHub Integration: Automatic deployments triggered by repository updates ensure your mail configuration stays current

Prerequisites

Before deploying Exim on Klutch.sh, ensure you have:

  • A Klutch.sh account with dashboard access at klutch.sh/app
  • A GitHub account for repository hosting
  • Docker installed locally for testing (optional but recommended)
  • Basic understanding of SMTP protocol and mail transfer agent concepts
  • Familiarity with mail routing rules and access control lists
  • Knowledge of DNS MX records and mail delivery fundamentals
  • Domain name(s) for production mail delivery (required for internet mail)
  • DNS access to configure MX, SPF, and DKIM records
  • Understanding of mail queue management and delivery retry strategies

Understanding Exim Architecture

Core Technology Stack

Exim is built on proven technologies for high-performance mail processing:

Primary Components:

  • C Language Implementation: Compiled code provides excellent performance and low resource overhead for mail processing
  • Single-Stage Queue: Efficient mail queue design with atomic operations for reliable message handling
  • ACL Engine: Advanced Access Control Lists for fine-grained mail acceptance and rejection policies
  • Router System: Flexible rule-based routing engine for determining mail delivery destinations
  • Transport Layer: Modular delivery mechanisms supporting SMTP, local delivery, pipes, and custom protocols
  • Authentication Framework: Support for PLAIN, LOGIN, CRAM-MD5, and external authentication mechanisms
  • Expansion Variables: Powerful string expansion system for dynamic configuration based on mail content

Key Features

Mail Routing and Delivery:

  • Multiple router types (dnslookup, manualroute, accept, redirect)
  • Virtual domain support with database lookups
  • Mail forwarding and aliasing with complex condition testing
  • Conditional routing based on sender, recipient, content, or headers
  • Support for multiple delivery attempts with configurable retry schedules

Security and Authentication:

  • TLS/SSL encryption for SMTP connections with certificate validation
  • SASL authentication for authenticated mail submission
  • SPF checking for sender verification
  • DKIM signing and verification for mail authentication
  • Sender and recipient address verification
  • Rate limiting and connection throttling

Content Processing:

  • Header and body scanning with condition matching
  • Integration with external content scanners (SpamAssassin, ClamAV)
  • Mail filtering based on size, content, or metadata
  • Data ACLs for content-based acceptance policies
  • Automatic message modification and header rewriting

Monitoring and Management:

  • Detailed logging of all mail transactions
  • Queue management tools (mailq, exiqgrep, exim -bp)
  • Real-time message tracking and delivery monitoring
  • Frozen message handling for problematic mail
  • Panic log for system-level errors

Standard Port Configuration

  • Port 25: Standard SMTP for receiving mail from other mail servers
  • Port 587: Mail submission (SMTP AUTH) for authenticated clients
  • Port 465: SMTPS (legacy) for SSL-encrypted mail submission
  • Internal Port: 25 (Exim’s default SMTP port)
  • External Access: Port 8000 on Klutch.sh routes to internal port 25

Preparing Your Repository

Step 1: Create Repository Structure

Create a new GitHub repository for your Exim deployment:

Terminal window
mkdir exim-deployment
cd exim-deployment
git init

Your repository structure should look like this:

exim-deployment/
├── Dockerfile
├── configure-exim.sh
├── exim.conf
├── dkim-setup.sh
├── .env.example
├── .dockerignore
└── README.md

Step 2: Create the Dockerfile

Create a Dockerfile in your repository root:

FROM debian:bookworm-slim
# Install Exim and required dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
exim4-daemon-heavy \
openssl \
ca-certificates \
dnsutils \
procps \
tini && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create necessary directories
RUN mkdir -p /var/spool/exim4 \
/var/log/exim4 \
/etc/exim4/dkim \
/etc/exim4/conf.d && \
chown -R Debian-exim:Debian-exim /var/spool/exim4 /var/log/exim4 /etc/exim4
# Copy configuration files
COPY configure-exim.sh /usr/local/bin/configure-exim.sh
COPY exim.conf /etc/exim4/exim4.conf.template
COPY dkim-setup.sh /usr/local/bin/dkim-setup.sh
# Make scripts executable
RUN chmod +x /usr/local/bin/configure-exim.sh /usr/local/bin/dkim-setup.sh
# Expose SMTP ports
EXPOSE 25 587 465
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD exim -bV || exit 1
# Use tini for proper signal handling
ENTRYPOINT ["/usr/bin/tini", "--"]
# Run configuration script and start Exim
CMD ["/bin/bash", "-c", "/usr/local/bin/configure-exim.sh && exec /usr/sbin/exim -bdf -q30m"]

Step 3: Create Configuration Script

Create configure-exim.sh to dynamically configure Exim at startup:

#!/bin/bash
set -e
echo "Configuring Exim mail transfer agent..."
# Set default values
PRIMARY_HOSTNAME="${PRIMARY_HOSTNAME:-mail.example.com}"
RELAY_DOMAINS="${RELAY_DOMAINS:-example.com}"
RELAY_TO_DOMAINS="${RELAY_TO_DOMAINS:-}"
SMARTHOST="${SMARTHOST:-}"
SMARTHOST_AUTH_USER="${SMARTHOST_AUTH_USER:-}"
SMARTHOST_AUTH_PASS="${SMARTHOST_AUTH_PASS:-}"
DKIM_SELECTOR="${DKIM_SELECTOR:-default}"
ENABLE_DKIM="${ENABLE_DKIM:-false}"
ENABLE_SPF="${ENABLE_SPF:-true}"
TRUSTED_HOSTS="${TRUSTED_HOSTS:-127.0.0.1 : ::1}"
MAX_MESSAGE_SIZE="${MAX_MESSAGE_SIZE:-50M}"
# Create main configuration file from template
cat /etc/exim4/exim4.conf.template | \
sed "s/{{PRIMARY_HOSTNAME}}/${PRIMARY_HOSTNAME}/g" | \
sed "s/{{RELAY_DOMAINS}}/${RELAY_DOMAINS}/g" | \
sed "s/{{RELAY_TO_DOMAINS}}/${RELAY_TO_DOMAINS}/g" | \
sed "s/{{SMARTHOST}}/${SMARTHOST}/g" | \
sed "s/{{DKIM_SELECTOR}}/${DKIM_SELECTOR}/g" | \
sed "s/{{ENABLE_DKIM}}/${ENABLE_DKIM}/g" | \
sed "s/{{ENABLE_SPF}}/${ENABLE_SPF}/g" | \
sed "s/{{TRUSTED_HOSTS}}/${TRUSTED_HOSTS}/g" | \
sed "s/{{MAX_MESSAGE_SIZE}}/${MAX_MESSAGE_SIZE}/g" \
> /etc/exim4/exim4.conf
# Configure smarthost authentication if provided
if [ -n "$SMARTHOST" ] && [ -n "$SMARTHOST_AUTH_USER" ] && [ -n "$SMARTHOST_AUTH_PASS" ]; then
echo "Configuring smarthost authentication..."
cat > /etc/exim4/passwd.client <<EOF
${SMARTHOST}:${SMARTHOST_AUTH_USER}:${SMARTHOST_AUTH_PASS}
EOF
chmod 640 /etc/exim4/passwd.client
chown Debian-exim:Debian-exim /etc/exim4/passwd.client
fi
# Setup DKIM if enabled
if [ "$ENABLE_DKIM" = "true" ]; then
echo "Setting up DKIM signing..."
/usr/local/bin/dkim-setup.sh
fi
# Set proper permissions
chown -R Debian-exim:Debian-exim /var/spool/exim4 /var/log/exim4
chmod 750 /var/spool/exim4 /var/log/exim4
# Test configuration
echo "Testing Exim configuration..."
exim -bV
exim -C /etc/exim4/exim4.conf -bV
echo "Exim configuration complete."

Step 4: Create Exim Configuration Template

Create exim.conf with a production-ready Exim configuration:

# Exim 4 Configuration for Klutch.sh Deployment
# Primary hostname for this mail server
primary_hostname = {{PRIMARY_HOSTNAME}}
# Qualify unqualified addresses with domain
qualify_domain = {{PRIMARY_HOSTNAME}}
qualify_recipient = {{PRIMARY_HOSTNAME}}
# Administrator email for system notifications
errors_address = postmaster@{{PRIMARY_HOSTNAME}}
# Never qualify with this domain
never_users = root
# Trusted users allowed to send mail as any sender
trusted_users = Debian-exim : www-data
# Hosts considered trusted for relay
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 5s
# TLS Configuration
tls_advertise_hosts = *
tls_certificate = /etc/ssl/certs/ssl-cert-snakeoil.pem
tls_privatekey = /etc/ssl/private/ssl-cert-snakeoil.key
# DKIM Configuration
.ifdef ENABLE_DKIM
dkim_selector = {{DKIM_SELECTOR}}
dkim_domain = {{PRIMARY_HOSTNAME}}
dkim_private_key = /etc/exim4/dkim/${lc:${domain}}.private
dkim_canon = relaxed
.endif
# Logging Configuration
log_selector = +all -subject -arguments
log_file_path = /var/log/exim4/%slog
# Message size limit
message_size_limit = {{MAX_MESSAGE_SIZE}}
# Freeze messages after too many delivery attempts
timeout_frozen_after = 7d
ignore_bounce_errors_after = 2d
######################################################################
# ACL CONFIGURATION #
######################################################################
# Access Control Lists
begin acl
acl_check_rcpt:
# Accept if local delivery
accept hosts = :
control = dkim_disable_verify
# Deny if remote server tries to relay
deny message = relay not permitted
!authenticated = *
!hosts = +relay_from_hosts
!domains = +relay_to_domains
# Accept authenticated senders
accept authenticated = *
control = submission
# Accept mail to our domains
accept domains = +relay_to_domains
# Deny all other relay attempts
deny message = relay not permitted
acl_check_data:
# Deny messages with suspicious content
deny condition = ${if match{$h_subject:}{(?i)(viagra|cialis|casino)}}
message = Message rejected due to content policy
# Accept the message
accept
######################################################################
# ROUTERS CONFIGURATION #
######################################################################
begin routers
# Smarthost router - route all mail via smarthost if configured
smarthost:
driver = manualroute
condition = ${if !eq{{{SMARTHOST}}}{}}}
domains = ! +local_domains
transport = remote_smtp_smarthost
route_list = * {{SMARTHOST}}
host_find_failed = defer
same_domain_copy_routing = yes
no_more
# DNS lookup router for remote domains
dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
same_domain_copy_routing = yes
no_verify
no_more
# System aliases
system_aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/aliases}}
file_transport = address_file
pipe_transport = address_pipe
# Local user delivery
localuser:
driver = accept
check_local_user
transport = local_delivery
cannot_route_message = Unknown user
######################################################################
# TRANSPORTS CONFIGURATION #
######################################################################
begin transports
# Remote SMTP delivery
remote_smtp:
driver = smtp
dkim_domain = {{PRIMARY_HOSTNAME}}
.ifdef ENABLE_DKIM
dkim_selector = {{DKIM_SELECTOR}}
dkim_private_key = /etc/exim4/dkim/${lc:${domain}}.private
dkim_canon = relaxed
.endif
# Smarthost SMTP delivery with authentication
remote_smtp_smarthost:
driver = smtp
hosts_require_auth = {{SMARTHOST}}
hosts_require_tls = {{SMARTHOST}}
.ifdef SMARTHOST_AUTH_USER
.endif
# Local maildir delivery
local_delivery:
driver = appendfile
directory = $home/Maildir
maildir_format
delivery_date_add
envelope_to_add
return_path_add
create_directory
directory_mode = 0700
mode = 0600
# Pipe transport for forwarding to programs
address_pipe:
driver = pipe
return_output
# File transport for saving to files
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
######################################################################
# RETRY CONFIGURATION #
######################################################################
begin retry
# Retry patterns for different scenarios
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
######################################################################
# REWRITE CONFIGURATION #
######################################################################
begin rewrite
# No rewriting by default
######################################################################
# AUTHENTICATION CONFIGURATION #
######################################################################
begin authenticators
# PLAIN authentication
plain:
driver = plaintext
server_set_id = $auth2
server_prompts = :
server_condition = ${if saslauthd{{$auth2}{$auth3}}{1}{0}}
server_advertise_condition = ${if def:tls_in_cipher }
# LOGIN authentication
login:
driver = plaintext
server_set_id = $auth1
server_prompts = Username:: : Password::
server_condition = ${if saslauthd{{$auth1}{$auth2}}{1}{0}}
server_advertise_condition = ${if def:tls_in_cipher }
# Client authentication for smarthost
smarthost_login:
driver = plaintext
public_name = LOGIN
.ifdef SMARTHOST_AUTH_USER
client_send = : {{SMARTHOST_AUTH_USER}} : {{SMARTHOST_AUTH_PASS}}
.endif

Step 5: Create DKIM Setup Script

Create dkim-setup.sh for generating DKIM keys:

#!/bin/bash
set -e
DKIM_DIR="/etc/exim4/dkim"
DOMAIN="${PRIMARY_HOSTNAME:-example.com}"
SELECTOR="${DKIM_SELECTOR:-default}"
echo "Setting up DKIM for domain: $DOMAIN"
mkdir -p "$DKIM_DIR"
# Generate DKIM key if it doesn't exist
if [ ! -f "$DKIM_DIR/${DOMAIN}.private" ]; then
echo "Generating new DKIM key pair..."
openssl genrsa -out "$DKIM_DIR/${DOMAIN}.private" 2048
openssl rsa -in "$DKIM_DIR/${DOMAIN}.private" -pubout -out "$DKIM_DIR/${DOMAIN}.public"
# Extract public key for DNS TXT record
echo "DKIM Public Key for DNS TXT Record:"
echo "Record Name: ${SELECTOR}._domainkey.${DOMAIN}"
echo "Record Type: TXT"
echo "Record Value:"
echo -n "v=DKIM1; k=rsa; p="
grep -v '^-' "$DKIM_DIR/${DOMAIN}.public" | tr -d '\n'
echo ""
fi
# Set proper permissions
chmod 640 "$DKIM_DIR/${DOMAIN}.private"
chmod 644 "$DKIM_DIR/${DOMAIN}.public"
chown -R Debian-exim:Debian-exim "$DKIM_DIR"
echo "DKIM setup complete."

Step 6: Create Environment Variables Example

Create .env.example to document configuration options:

# Primary Configuration
PRIMARY_HOSTNAME=mail.example.com
RELAY_DOMAINS=example.com:example.org
RELAY_TO_DOMAINS=example.com:example.org
# Smarthost Configuration (Optional - for relay through another mail server)
SMARTHOST=smtp.gmail.com::587
SMARTHOST_AUTH_USER=your-email@gmail.com
SMARTHOST_AUTH_PASS=your-app-password
# DKIM Configuration
ENABLE_DKIM=true
DKIM_SELECTOR=default
# SPF Configuration
ENABLE_SPF=true
# Security Settings
TRUSTED_HOSTS=127.0.0.1 : ::1 : 10.0.0.0/8
MAX_MESSAGE_SIZE=50M
# TLS/SSL Configuration
TLS_CERTIFICATE_PATH=/etc/ssl/certs/mail.crt
TLS_PRIVATE_KEY_PATH=/etc/ssl/private/mail.key
# Logging Configuration
LOG_LEVEL=info

Step 7: Create .dockerignore

Create .dockerignore to exclude unnecessary files:

.git
.github
.env
*.md
.gitignore
docker-compose.yml
.dockerignore
node_modules

Step 8: Create README

Create README.md to document your deployment:

# Exim Mail Transfer Agent on Klutch.sh
High-performance mail transfer agent with advanced routing and filtering capabilities.
## Features
- SMTP message acceptance and delivery
- Virtual domain support
- DKIM signing and SPF verification
- TLS/SSL encryption
- Smarthost relay support
- Advanced mail filtering with ACLs
- Persistent mail queue storage
## Configuration
### Required Environment Variables
- `PRIMARY_HOSTNAME`: Your mail server's fully qualified domain name
- `RELAY_DOMAINS`: Domains to accept mail for (colon-separated)
### Optional Environment Variables
- `SMARTHOST`: External SMTP server for relaying (format: host::port)
- `SMARTHOST_AUTH_USER`: Username for smarthost authentication
- `SMARTHOST_AUTH_PASS`: Password for smarthost authentication
- `ENABLE_DKIM`: Enable DKIM signing (default: false)
- `DKIM_SELECTOR`: DKIM selector for DNS records (default: default)
- `ENABLE_SPF`: Enable SPF checking (default: true)
- `TRUSTED_HOSTS`: Hosts allowed to relay (default: 127.0.0.1 : ::1)
- `MAX_MESSAGE_SIZE`: Maximum message size (default: 50M)
## Deployment on Klutch.sh
1. Push this repository to GitHub
2. Create new app in Klutch.sh dashboard
3. Select this repository
4. Choose **TCP** traffic type
5. Set internal port to **25**
6. Add environment variables from `.env.example`
7. Attach persistent volume at `/var/spool/exim4` (recommended: 10GB)
8. Deploy and configure DNS MX records
## DNS Configuration
### MX Record

example.com IN MX 10 example-app.klutch.sh.

### SPF Record

example.com IN TXT “v=spf1 mx ~all”

### DKIM Record (if enabled)

default._domainkey.example.com IN TXT “v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY”

## Testing
### Send Test Email
```bash
telnet example-app.klutch.sh 8000
EHLO test.local
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
DATA
Subject: Test Email
This is a test message.
.
QUIT

Check Mail Queue

Terminal window
docker exec <container-id> exim -bp

View Mail Logs

Terminal window
docker exec <container-id> tail -f /var/log/exim4/mainlog

Support

For issues and questions:

---
## Deploying to Klutch.sh
### Deployment Steps
<ol>
<li><strong>Log in to Klutch.sh</strong>
Navigate to <a href="https://klutch.sh/app" target="_blank">klutch.sh/app</a> and sign in to your account.
</li>
<li><strong>Create a New Project</strong>
From your dashboard, create a new project. Give it a descriptive name like "Mail Infrastructure" or "Email Services".
</li>
<li><strong>Create a New App</strong>
Within your project, click "Create New App" and provide a meaningful name like "exim-mail-server".
</li>
<li><strong>Connect Your Repository</strong>
- Select **GitHub** as your Git source
- Choose the repository containing your Exim Dockerfile
- Select the branch to deploy (typically `main` or `master`)
Klutch.sh will automatically detect the Dockerfile in your repository root.
</li>
<li><strong>Configure Traffic Type</strong>
Select **TCP** as your traffic type since Exim uses SMTP protocol for mail transfer.
</li>
<li><strong>Configure Port Mapping</strong>
Set the **internal port** to **25** (Exim's default SMTP port).
Your Exim server will be accessible on **TCP port 8000** externally via `example-app.klutch.sh:8000`. Configure mail clients and other mail servers to connect to this external endpoint.
</li>
<li><strong>Set Environment Variables</strong>
Add the following environment variables in the Klutch.sh dashboard:
**Essential Variables:**
- `PRIMARY_HOSTNAME`: Your mail server's FQDN (e.g., `mail.example.com`)
- `RELAY_DOMAINS`: Domains to accept mail for (e.g., `example.com:example.org`)
- `RELAY_TO_DOMAINS`: Same as RELAY_DOMAINS for most setups
**Smarthost Configuration (if routing through external SMTP):**
- `SMARTHOST`: External SMTP server (e.g., `smtp.gmail.com::587`)
- `SMARTHOST_AUTH_USER`: Authentication username
- `SMARTHOST_AUTH_PASS`: Authentication password (mark as secret)
**Security and Features:**
- `ENABLE_DKIM`: Set to `true` to enable DKIM signing
- `DKIM_SELECTOR`: DKIM selector (default: `default`)
- `TRUSTED_HOSTS`: Hosts allowed to relay (e.g., `127.0.0.1 : ::1 : 10.0.0.0/8`)
- `MAX_MESSAGE_SIZE`: Maximum message size (e.g., `50M`)
Mark sensitive variables like passwords as **secrets** for encrypted storage.
</li>
<li><strong>Attach Persistent Volume</strong>
This is critical for preserving mail queues and spool files across deployments:
**Mail Spool Volume:**
- **Mount Path**: `/var/spool/exim4`
- **Size**: 10GB minimum (scale based on expected mail volume)
**Logs Volume (optional but recommended):**
- **Mount Path**: `/var/log/exim4`
- **Size**: 5GB
Persistent volumes ensure mail in the queue is not lost during container restarts or redeployments.
</li>
<li><strong>Configure Compute Resources</strong>
Select appropriate resources based on mail volume:
- **Minimum**: 1 CPU, 1GB RAM (small deployments, < 100 emails/day)
- **Recommended**: 2 CPU, 2GB RAM (production use, moderate volume)
- **High-Scale**: 4+ CPU, 4GB+ RAM (high volume, > 10,000 emails/day)
</li>
<li><strong>Deploy Your Application</strong>
Click **"Create"** to start the deployment. Klutch.sh will:
- Clone your repository from GitHub
- Build the Docker image using your Dockerfile
- Apply environment variables
- Attach persistent volumes
- Start the Exim container
- Expose TCP port 8000 for external SMTP access
Monitor the deployment logs for any errors during startup.
</li>
<li><strong>Configure DNS Records</strong>
After successful deployment, configure DNS records for your domain:
**MX Record** (required for receiving mail):

example.com. IN MX 10 example-app.klutch.sh.

**SPF Record** (recommended for sender authentication):

example.com. IN TXT “v=spf1 mx -all”

**DKIM Record** (if DKIM enabled - get public key from container logs):

default._domainkey.example.com. IN TXT “v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY_HERE”

**DMARC Record** (recommended for policy enforcement):

_dmarc.example.com. IN TXT “v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com”

Allow 24-48 hours for DNS propagation before testing mail delivery.
</li>
<li><strong>Verify Deployment</strong>
Test your mail server is accepting connections:
```bash
telnet example-app.klutch.sh 8000

You should see an SMTP greeting from Exim. Type QUIT to exit.

Monitor logs in the Klutch.sh dashboard to verify Exim is processing mail correctly.


Getting Started with Exim

Initial Configuration Test

After deployment, test basic SMTP functionality:

Terminal window
# Connect to your Exim server
telnet example-app.klutch.sh 8000
# You should see output like:
# 220 mail.example.com ESMTP Exim 4.96 ...
# Test SMTP commands
EHLO test.local
MAIL FROM:<test@example.com>
RCPT TO:<recipient@example.com>
DATA
Subject: Test Email
From: test@example.com
To: recipient@example.com
This is a test message from Exim.
.
QUIT

Checking Mail Queue

Access your container to manage the mail queue:

Terminal window
# View all messages in queue
docker exec -it <container-id> exim -bp
# Count messages in queue
docker exec -it <container-id> exim -bpc
# View specific message
docker exec -it <container-id> exim -Mvc <message-id>
# Force queue run
docker exec -it <container-id> exim -qff
# Remove frozen messages
docker exec -it <container-id> exim -Mrm <message-id>

Retrieving DKIM Public Key

If you enabled DKIM signing, retrieve your public key for DNS:

Terminal window
# Get DKIM public key from container
docker exec <container-id> cat /etc/exim4/dkim/example.com.public
# Format for DNS TXT record:
# Record Name: default._domainkey.example.com
# Record Type: TXT
# Record Value: v=DKIM1; k=rsa; p=<YOUR_PUBLIC_KEY>

Monitoring Mail Logs

Track mail activity through Exim logs:

Terminal window
# Main log (all SMTP transactions)
docker exec -it <container-id> tail -f /var/log/exim4/mainlog
# Reject log (rejected messages)
docker exec -it <container-id> tail -f /var/log/exim4/rejectlog
# Panic log (system errors)
docker exec -it <container-id> tail -f /var/log/exim4/paniclog

Testing with Different Mail Clients

Using Mail Command (Linux/macOS):

Terminal window
echo "Test message body" | mail -s "Test Subject" \
-r sender@example.com \
recipient@example.com

Using Python SMTP:

import smtplib
from email.mime.text import MIMEText
msg = MIMEText("This is a test email from Python")
msg['Subject'] = 'Test Email'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
with smtplib.SMTP('example-app.klutch.sh', 8000) as server:
server.send_message(msg)
print("Email sent successfully")

Using Node.js Nodemailer:

const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'example-app.klutch.sh',
port: 8000,
secure: false, // Set to true if using TLS
});
const mailOptions = {
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Test Email',
text: 'This is a test email from Node.js'
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log('Error:', error);
} else {
console.log('Email sent:', info.response);
}
});

Advanced Configuration

Configuring Virtual Domains

Support multiple domains on a single Exim instance:

Update your environment variables:

RELAY_DOMAINS=domain1.com:domain2.com:domain3.com
RELAY_TO_DOMAINS=domain1.com:domain2.com:domain3.com

Modify exim.conf to add domain-specific routing:

# In the routers section, add before dnslookup:
virtual_domains:
driver = redirect
domains = domain2.com : domain3.com
data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/virtual_aliases}}
file_transport = address_file
pipe_transport = address_pipe

Create virtual aliases file /etc/exim4/virtual_aliases:

user@domain2.com: actualuser@domain1.com
admin@domain3.com: root@domain1.com

Integrating with SpamAssassin

Add spam filtering to your Exim configuration:

Install SpamAssassin in your Dockerfile:

RUN apt-get update && \
apt-get install -y spamassassin spamc && \
apt-get clean

Add ACL rule in exim.conf:

acl_check_data:
# Scan with SpamAssassin
warn spam = nobody:true
add_header = X-Spam-Score: $spam_score_int
add_header = X-Spam-Report: $spam_report
# Reject high-scoring spam
deny condition = ${if >{$spam_score_int}{100}}
message = This message scored $spam_score spam points
accept

Configuring Authenticated SMTP

Enable authentication for mail submission:

Update exim.conf authenticators section:

begin authenticators
# Use system passwords
plain:
driver = plaintext
server_set_id = $auth2
server_prompts = :
server_condition = ${if crypteq{$auth3}{${extract{1}{:}{${lookup{$auth2}lsearch{/etc/exim4/passwd}{$value}{*:*}}}}}}
server_advertise_condition = ${if def:tls_in_cipher}
# Use external authentication file
plain_file:
driver = plaintext
server_set_id = $auth2
server_prompts = :
server_condition = ${if eq{$auth3}{${extract{2}{:}{${lookup{$auth2}lsearch{/etc/exim4/passwd}{$value}}}}}}{yes}{no}}
server_advertise_condition = ${if def:tls_in_cipher}

Create password file /etc/exim4/passwd:

user@example.com:$apr1$ABC$XYZ123...

Generate password hashes:

Terminal window
openssl passwd -apr1 "your-password"

Rate Limiting and Connection Control

Prevent mail abuse with connection limits:

Add to exim.conf main section:

# Connection rate limits
smtp_accept_max = 20
smtp_accept_max_per_host = 5
smtp_accept_max_per_connection = 50
# Recipient rate limits
smtp_accept_max_per_host = 10
smtp_ratelimit_hosts = *
smtp_ratelimit_mail = 100,1h,strict
smtp_ratelimit_rcpt = 50,1h,per_mail
# Reject connections from blacklisted hosts
host_reject_connection = ${if exists{/etc/exim4/blacklist}{lsearch;/etc/exim4/blacklist}{}}

Custom Mail Routing Rules

Implement complex routing based on conditions:

# Route based on sender domain
conditional_router:
driver = manualroute
domains = ! +local_domains
condition = ${if match_domain{$sender_address_domain}{special-domain.com}}
route_list = * special-mx.example.com byname
transport = remote_smtp
# Route based on recipient
priority_router:
driver = manualroute
condition = ${if match{$local_part}{^priority-}}
route_list = * priority-server.example.com::25
transport = remote_smtp
no_more

Production Best Practices

Security Hardening

TLS/SSL Configuration:

Generate or obtain proper SSL certificates:

Terminal window
# Self-signed certificate (for testing only)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/mail.key \
-out /etc/ssl/certs/mail.crt
# Let's Encrypt (recommended for production)
# Configure certificate renewal automation

Update environment variables:

TLS_CERTIFICATE_PATH=/etc/ssl/certs/mail.crt
TLS_PRIVATE_KEY_PATH=/etc/ssl/private/mail.key

Access Control:

  • Implement strict ACLs to prevent open relay
  • Enable authentication for mail submission
  • Use SPF, DKIM, and DMARC for sender verification
  • Regularly update blacklist of known spam sources
  • Enable rate limiting to prevent abuse

Credential Management:

  • Store sensitive variables (passwords, API keys) as secrets in Klutch.sh
  • Rotate DKIM keys annually
  • Use strong, unique passwords for authentication
  • Limit access to configuration files and logs

Performance Optimization

Queue Management:

# Optimize queue runner frequency
queue_run_max = 5
queue_run_in_order
deliver_queue_load_max = 8.0
queue_only_load = 10.0

Resource Allocation:

  • Monitor mail queue depth and adjust compute resources
  • Use persistent volumes with SSD storage for mail spool
  • Implement queue prioritization for important mail
  • Configure retry rules to balance delivery attempts

Connection Pooling:

# Reuse SMTP connections
smtp_connect_backoff = yes
remote_max_parallel = 10

Monitoring and Alerting

Key Metrics to Monitor:

  • Mail queue depth (alert if > 100 messages)
  • Delivery success/failure rates
  • SMTP connection attempts and rejections
  • Disk space on mail spool volume
  • Memory and CPU usage
  • TLS handshake failures

Log Analysis:

Terminal window
# Count delivered messages
grep "Completed" /var/log/exim4/mainlog | wc -l
# Check for delivery failures
grep "failed" /var/log/exim4/mainlog
# Monitor authentication attempts
grep "authenticator" /var/log/exim4/mainlog
# Track rejected messages
tail -n 100 /var/log/exim4/rejectlog

Automated Health Checks:

Create a monitoring script:

#!/bin/bash
# Monitor Exim health
QUEUE_COUNT=$(exim -bpc)
if [ $QUEUE_COUNT -gt 100 ]; then
echo "WARNING: Mail queue has $QUEUE_COUNT messages"
fi
# Check if Exim is responding
timeout 5 bash -c "</dev/tcp/localhost/25" && echo "Exim is responding" || echo "ERROR: Exim not responding"
# Check for panic log entries
if [ -s /var/log/exim4/paniclog ]; then
echo "ERROR: Panic log has entries"
fi

Backup and Disaster Recovery

Critical Data to Backup:

  • Mail queue and spool: /var/spool/exim4
  • Configuration files: /etc/exim4
  • DKIM keys: /etc/exim4/dkim
  • Authentication databases: /etc/exim4/passwd
  • Mail logs: /var/log/exim4

Backup Strategy:

#!/bin/bash
# Backup script for Exim data
BACKUP_DIR="/backup/exim"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Freeze queue to prevent changes during backup
exim -R all freeze
# Backup mail spool
tar -czf "${BACKUP_DIR}/spool-${TIMESTAMP}.tar.gz" /var/spool/exim4
# Backup configuration
tar -czf "${BACKUP_DIR}/config-${TIMESTAMP}.tar.gz" /etc/exim4
# Thaw queue
exim -R all thaw
# Rotate old backups (keep last 7 days)
find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +7 -delete

Disaster Recovery Plan:

  1. Redeploy application from GitHub repository
  2. Restore mail spool from latest backup
  3. Restore configuration and DKIM keys
  4. Update environment variables in Klutch.sh
  5. Verify DNS records are correct
  6. Test mail sending and receiving
  7. Monitor logs for delivery of queued messages

Scaling Considerations

Horizontal Scaling:

For high-volume mail processing:

  • Deploy multiple Exim instances
  • Use DNS round-robin for MX records
  • Implement load balancing for outbound mail
  • Consider dedicated inbound and outbound servers

Vertical Scaling:

Increase resources on Klutch.sh:

  • Scale CPU for mail processing throughput
  • Increase RAM for larger mail queues
  • Expand storage for mail spool and logs
  • Monitor resource usage and adjust accordingly

Troubleshooting

Common Issues and Solutions

Issue: Exim Not Starting

Check container logs for error messages:

Terminal window
# View startup logs
docker logs <container-id>
# Test configuration syntax
docker exec <container-id> exim -bV
docker exec <container-id> exim -C /etc/exim4/exim4.conf -bV

Solution:

  • Verify all environment variables are set correctly
  • Check configuration file syntax
  • Ensure persistent volumes are properly mounted
  • Verify file permissions on /var/spool/exim4

Issue: Mail Not Being Delivered

Check mail queue and logs:

Terminal window
# View messages in queue
docker exec <container-id> exim -bp
# Check delivery attempts
docker exec <container-id> grep "delivery" /var/log/exim4/mainlog
# View specific message details
docker exec <container-id> exim -Mvh <message-id>
docker exec <container-id> exim -Mvb <message-id>

Solution:

  • Verify DNS MX records are configured correctly
  • Check destination server is accepting mail
  • Review reject log for why messages are being rejected
  • Verify smarthost configuration if using relay
  • Test network connectivity to destination servers

Issue: Authentication Failures

Review authentication logs:

Terminal window
# Check authentication attempts
docker exec <container-id> grep "authenticator" /var/log/exim4/mainlog
# Test authentication manually
docker exec <container-id> exim -bh <client-ip>

Solution:

  • Verify username and password are correct
  • Check TLS is enabled for authentication
  • Review authenticator configuration in exim.conf
  • Ensure password file has correct permissions (640)
  • Test with different authentication mechanisms

Issue: Messages Stuck in Queue

Analyze queue and force delivery:

Terminal window
# View frozen messages
docker exec <container-id> exim -bp | grep frozen
# Thaw frozen messages
docker exec <container-id> exim -Mt <message-id>
# Force delivery attempt
docker exec <container-id> exim -M <message-id>
# Force queue run
docker exec <container-id> exim -qff

Solution:

  • Check why messages were frozen (review logs)
  • Verify destination servers are reachable
  • Increase retry timeout if temporary failures
  • Remove invalid messages from queue
  • Check disk space on mail spool volume

Issue: High Memory or CPU Usage

Monitor resource consumption:

Terminal window
# Check process stats
docker exec <container-id> ps aux | grep exim
# View queue count
docker exec <container-id> exim -bpc
# Check for runaway processes
docker stats <container-id>

Solution:

  • Reduce queue runner frequency if high load
  • Implement rate limiting to prevent abuse
  • Scale compute resources in Klutch.sh
  • Optimize configuration for lower resource usage
  • Check for mail loops or delivery issues

Issue: TLS/SSL Certificate Errors

Verify certificate configuration:

Terminal window
# Test TLS handshake
openssl s_client -connect example-app.klutch.sh:8000 -starttls smtp
# Check certificate details
docker exec <container-id> openssl x509 -in /etc/ssl/certs/mail.crt -text -noout

Solution:

  • Ensure certificate files exist and are readable
  • Verify certificate is not expired
  • Check certificate matches hostname
  • Use proper certificate chain (include intermediates)
  • Test with self-signed cert for debugging

Issue: DKIM Signature Failures

Verify DKIM configuration:

Terminal window
# Check DKIM keys exist
docker exec <container-id> ls -la /etc/exim4/dkim/
# Test DKIM signing
docker exec <container-id> exim -d-all+deliver -bt recipient@example.com
# Verify DNS DKIM record
dig TXT default._domainkey.example.com

Solution:

  • Ensure DKIM private key has correct permissions (640)
  • Verify DKIM public key is published in DNS
  • Check DKIM selector matches DNS record name
  • Review Exim DKIM configuration syntax
  • Test DKIM with online verification tools

Updating Your Exim Deployment

Rolling Updates

To update your Exim configuration or version:

  1. Make changes to your repository (Dockerfile, configuration files)
  2. Commit and push to GitHub
  3. In Klutch.sh dashboard, trigger a new deployment
  4. Monitor deployment logs for successful startup
  5. Verify mail queue was preserved in persistent volume
  6. Test mail sending and receiving functionality

Version Upgrades

When upgrading Exim versions:

# Update Exim version in Dockerfile
RUN apt-get update && \
apt-get install -y exim4-daemon-heavy=4.97-1 && \
apt-get clean

Pre-upgrade checklist:

  • Review Exim changelog for breaking changes
  • Backup mail queue and configuration
  • Test configuration syntax with new version
  • Plan for brief service interruption
  • Have rollback plan ready

Post-upgrade verification:

  • Check all mail protocols are working (SMTP, submission)
  • Verify authentication still functions
  • Test DKIM signing if enabled
  • Review logs for any errors or warnings
  • Monitor queue processing for issues

Additional Resources


Conclusion

Deploying Exim on Klutch.sh provides a robust, highly configurable mail transfer agent for your email infrastructure needs. With automated Docker detection, persistent storage for mail queues, flexible configuration through environment variables, and TCP traffic support, Klutch.sh simplifies Exim deployment while maintaining production-grade reliability and security.

Throughout this guide, you’ve learned how to:

  • Create a production-ready Dockerfile for Exim with comprehensive configuration
  • Configure advanced mail routing rules and access control lists
  • Implement DKIM signing and SPF verification for email authentication
  • Set up persistent volumes for mail spool and queue management
  • Secure your mail server with TLS/SSL encryption and authentication
  • Deploy and manage Exim on Klutch.sh with proper DNS configuration
  • Monitor mail delivery and troubleshoot common issues
  • Integrate Exim with external applications and smarthost relays
  • Optimize performance and implement best practices for production deployments

Exim’s flexible configuration system combined with Klutch.sh’s streamlined deployment workflow enables you to build sophisticated mail routing systems, implement complex filtering rules, and maintain reliable email delivery at scale. Start with the basic configuration provided in this guide, then customize Exim’s powerful features to match your specific requirements.

For questions, issues, or feedback about deploying Exim on Klutch.sh, consult the official Exim documentation, engage with the Exim community mailing lists, or contact Klutch.sh support for deployment-specific guidance.