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:
mkdir exim-deploymentcd exim-deploymentgit initYour repository structure should look like this:
exim-deployment/├── Dockerfile├── configure-exim.sh├── exim.conf├── dkim-setup.sh├── .env.example├── .dockerignore└── README.mdStep 2: Create the Dockerfile
Create a Dockerfile in your repository root:
FROM debian:bookworm-slim
# Install Exim and required dependenciesRUN 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 directoriesRUN 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 filesCOPY configure-exim.sh /usr/local/bin/configure-exim.shCOPY exim.conf /etc/exim4/exim4.conf.templateCOPY dkim-setup.sh /usr/local/bin/dkim-setup.sh
# Make scripts executableRUN chmod +x /usr/local/bin/configure-exim.sh /usr/local/bin/dkim-setup.sh
# Expose SMTP portsEXPOSE 25 587 465
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD exim -bV || exit 1
# Use tini for proper signal handlingENTRYPOINT ["/usr/bin/tini", "--"]
# Run configuration script and start EximCMD ["/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/bashset -e
echo "Configuring Exim mail transfer agent..."
# Set default valuesPRIMARY_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 templatecat /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 providedif [ -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.clientfi
# Setup DKIM if enabledif [ "$ENABLE_DKIM" = "true" ]; then echo "Setting up DKIM signing..." /usr/local/bin/dkim-setup.shfi
# Set proper permissionschown -R Debian-exim:Debian-exim /var/spool/exim4 /var/log/exim4chmod 750 /var/spool/exim4 /var/log/exim4
# Test configurationecho "Testing Exim configuration..."exim -bVexim -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 serverprimary_hostname = {{PRIMARY_HOSTNAME}}
# Qualify unqualified addresses with domainqualify_domain = {{PRIMARY_HOSTNAME}}qualify_recipient = {{PRIMARY_HOSTNAME}}
# Administrator email for system notificationserrors_address = postmaster@{{PRIMARY_HOSTNAME}}
# Never qualify with this domainnever_users = root
# Trusted users allowed to send mail as any sendertrusted_users = Debian-exim : www-data
# Hosts considered trusted for relayhost_lookup = *rfc1413_hosts = *rfc1413_query_timeout = 5s
# TLS Configurationtls_advertise_hosts = *tls_certificate = /etc/ssl/certs/ssl-cert-snakeoil.pemtls_privatekey = /etc/ssl/private/ssl-cert-snakeoil.key
# DKIM Configuration.ifdef ENABLE_DKIMdkim_selector = {{DKIM_SELECTOR}}dkim_domain = {{PRIMARY_HOSTNAME}}dkim_private_key = /etc/exim4/dkim/${lc:${domain}}.privatedkim_canon = relaxed.endif
# Logging Configurationlog_selector = +all -subject -argumentslog_file_path = /var/log/exim4/%slog
# Message size limitmessage_size_limit = {{MAX_MESSAGE_SIZE}}
# Freeze messages after too many delivery attemptstimeout_frozen_after = 7dignore_bounce_errors_after = 2d
####################################################################### ACL CONFIGURATION #######################################################################
# Access Control Listsbegin 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 configuredsmarthost: 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 domainsdnslookup: driver = dnslookup domains = ! +local_domains transport = remote_smtp same_domain_copy_routing = yes no_verify no_more
# System aliasessystem_aliases: driver = redirect allow_fail allow_defer data = ${lookup{$local_part}lsearch{/etc/aliases}} file_transport = address_file pipe_transport = address_pipe
# Local user deliverylocaluser: driver = accept check_local_user transport = local_delivery cannot_route_message = Unknown user
####################################################################### TRANSPORTS CONFIGURATION #######################################################################
begin transports
# Remote SMTP deliveryremote_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 authenticationremote_smtp_smarthost: driver = smtp hosts_require_auth = {{SMARTHOST}} hosts_require_tls = {{SMARTHOST}} .ifdef SMARTHOST_AUTH_USER .endif
# Local maildir deliverylocal_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 programsaddress_pipe: driver = pipe return_output
# File transport for saving to filesaddress_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 authenticationplain: 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 authenticationlogin: 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 smarthostsmarthost_login: driver = plaintext public_name = LOGIN .ifdef SMARTHOST_AUTH_USER client_send = : {{SMARTHOST_AUTH_USER}} : {{SMARTHOST_AUTH_PASS}} .endifStep 5: Create DKIM Setup Script
Create dkim-setup.sh for generating DKIM keys:
#!/bin/bashset -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 existif [ ! -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 permissionschmod 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 ConfigurationPRIMARY_HOSTNAME=mail.example.comRELAY_DOMAINS=example.com:example.orgRELAY_TO_DOMAINS=example.com:example.org
# Smarthost Configuration (Optional - for relay through another mail server)SMARTHOST=smtp.gmail.com::587SMARTHOST_AUTH_USER=your-email@gmail.comSMARTHOST_AUTH_PASS=your-app-password
# DKIM ConfigurationENABLE_DKIM=trueDKIM_SELECTOR=default
# SPF ConfigurationENABLE_SPF=true
# Security SettingsTRUSTED_HOSTS=127.0.0.1 : ::1 : 10.0.0.0/8MAX_MESSAGE_SIZE=50M
# TLS/SSL ConfigurationTLS_CERTIFICATE_PATH=/etc/ssl/certs/mail.crtTLS_PRIVATE_KEY_PATH=/etc/ssl/private/mail.key
# Logging ConfigurationLOG_LEVEL=infoStep 7: Create .dockerignore
Create .dockerignore to exclude unnecessary files:
.git.github.env*.md.gitignoredocker-compose.yml.dockerignorenode_modulesStep 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 GitHub2. Create new app in Klutch.sh dashboard3. Select this repository4. Choose **TCP** traffic type5. 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 Recordexample.com IN MX 10 example-app.klutch.sh.
### SPF Recordexample.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```bashtelnet example-app.klutch.sh 8000EHLO test.localMAIL FROM:<sender@example.com>RCPT TO:<recipient@example.com>DATASubject: Test EmailThis is a test message..QUITCheck Mail Queue
docker exec <container-id> exim -bpView Mail Logs
docker exec <container-id> tail -f /var/log/exim4/mainlogSupport
For issues and questions:
- Exim Documentation: https://www.exim.org/docs.html
- Exim Wiki: http://wiki.exim.org/
- Klutch.sh Support: https://klutch.sh/support
---
## 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:
```bashtelnet example-app.klutch.sh 8000You 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:
# Connect to your Exim servertelnet example-app.klutch.sh 8000
# You should see output like:# 220 mail.example.com ESMTP Exim 4.96 ...
# Test SMTP commandsEHLO test.localMAIL FROM:<test@example.com>RCPT TO:<recipient@example.com>DATASubject: Test EmailFrom: test@example.comTo: recipient@example.com
This is a test message from Exim..QUITChecking Mail Queue
Access your container to manage the mail queue:
# View all messages in queuedocker exec -it <container-id> exim -bp
# Count messages in queuedocker exec -it <container-id> exim -bpc
# View specific messagedocker exec -it <container-id> exim -Mvc <message-id>
# Force queue rundocker exec -it <container-id> exim -qff
# Remove frozen messagesdocker exec -it <container-id> exim -Mrm <message-id>Retrieving DKIM Public Key
If you enabled DKIM signing, retrieve your public key for DNS:
# Get DKIM public key from containerdocker 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:
# 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/paniclogTesting with Different Mail Clients
Using Mail Command (Linux/macOS):
echo "Test message body" | mail -s "Test Subject" \ -r sender@example.com \ recipient@example.comUsing Python SMTP:
import smtplibfrom 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.comRELAY_TO_DOMAINS=domain1.com:domain2.com:domain3.comModify 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_pipeCreate virtual aliases file /etc/exim4/virtual_aliases:
user@domain2.com: actualuser@domain1.comadmin@domain3.com: root@domain1.comIntegrating 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 cleanAdd 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
acceptConfiguring Authenticated SMTP
Enable authentication for mail submission:
Update exim.conf authenticators section:
begin authenticators
# Use system passwordsplain: 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 fileplain_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:
openssl passwd -apr1 "your-password"Rate Limiting and Connection Control
Prevent mail abuse with connection limits:
Add to exim.conf main section:
# Connection rate limitssmtp_accept_max = 20smtp_accept_max_per_host = 5smtp_accept_max_per_connection = 50
# Recipient rate limitssmtp_accept_max_per_host = 10smtp_ratelimit_hosts = *smtp_ratelimit_mail = 100,1h,strictsmtp_ratelimit_rcpt = 50,1h,per_mail
# Reject connections from blacklisted hostshost_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 domainconditional_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 recipientpriority_router: driver = manualroute condition = ${if match{$local_part}{^priority-}} route_list = * priority-server.example.com::25 transport = remote_smtp no_moreProduction Best Practices
Security Hardening
TLS/SSL Configuration:
Generate or obtain proper SSL certificates:
# 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 automationUpdate environment variables:
TLS_CERTIFICATE_PATH=/etc/ssl/certs/mail.crtTLS_PRIVATE_KEY_PATH=/etc/ssl/private/mail.keyAccess 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 frequencyqueue_run_max = 5queue_run_in_orderdeliver_queue_load_max = 8.0queue_only_load = 10.0Resource 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 connectionssmtp_connect_backoff = yesremote_max_parallel = 10Monitoring 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:
# Count delivered messagesgrep "Completed" /var/log/exim4/mainlog | wc -l
# Check for delivery failuresgrep "failed" /var/log/exim4/mainlog
# Monitor authentication attemptsgrep "authenticator" /var/log/exim4/mainlog
# Track rejected messagestail -n 100 /var/log/exim4/rejectlogAutomated 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 respondingtimeout 5 bash -c "</dev/tcp/localhost/25" && echo "Exim is responding" || echo "ERROR: Exim not responding"
# Check for panic log entriesif [ -s /var/log/exim4/paniclog ]; then echo "ERROR: Panic log has entries"fiBackup 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 backupexim -R all freeze
# Backup mail spooltar -czf "${BACKUP_DIR}/spool-${TIMESTAMP}.tar.gz" /var/spool/exim4
# Backup configurationtar -czf "${BACKUP_DIR}/config-${TIMESTAMP}.tar.gz" /etc/exim4
# Thaw queueexim -R all thaw
# Rotate old backups (keep last 7 days)find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +7 -deleteDisaster Recovery Plan:
- Redeploy application from GitHub repository
- Restore mail spool from latest backup
- Restore configuration and DKIM keys
- Update environment variables in Klutch.sh
- Verify DNS records are correct
- Test mail sending and receiving
- 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:
# View startup logsdocker logs <container-id>
# Test configuration syntaxdocker exec <container-id> exim -bVdocker exec <container-id> exim -C /etc/exim4/exim4.conf -bVSolution:
- 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:
# View messages in queuedocker exec <container-id> exim -bp
# Check delivery attemptsdocker exec <container-id> grep "delivery" /var/log/exim4/mainlog
# View specific message detailsdocker 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:
# Check authentication attemptsdocker exec <container-id> grep "authenticator" /var/log/exim4/mainlog
# Test authentication manuallydocker 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:
# View frozen messagesdocker exec <container-id> exim -bp | grep frozen
# Thaw frozen messagesdocker exec <container-id> exim -Mt <message-id>
# Force delivery attemptdocker exec <container-id> exim -M <message-id>
# Force queue rundocker exec <container-id> exim -qffSolution:
- 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:
# Check process statsdocker exec <container-id> ps aux | grep exim
# View queue countdocker exec <container-id> exim -bpc
# Check for runaway processesdocker 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:
# Test TLS handshakeopenssl s_client -connect example-app.klutch.sh:8000 -starttls smtp
# Check certificate detailsdocker exec <container-id> openssl x509 -in /etc/ssl/certs/mail.crt -text -nooutSolution:
- 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:
# Check DKIM keys existdocker exec <container-id> ls -la /etc/exim4/dkim/
# Test DKIM signingdocker exec <container-id> exim -d-all+deliver -bt recipient@example.com
# Verify DNS DKIM recorddig TXT default._domainkey.example.comSolution:
- 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:
- Make changes to your repository (Dockerfile, configuration files)
- Commit and push to GitHub
- In Klutch.sh dashboard, trigger a new deployment
- Monitor deployment logs for successful startup
- Verify mail queue was preserved in persistent volume
- Test mail sending and receiving functionality
Version Upgrades
When upgrading Exim versions:
# Update Exim version in DockerfileRUN apt-get update && \ apt-get install -y exim4-daemon-heavy=4.97-1 && \ apt-get cleanPre-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
- Exim Official Website
- Exim Documentation
- Exim Wiki
- Exim GitHub Repository
- Exim Specification
- Exim Mailing Lists
- Klutch.sh Volumes Documentation
- Klutch.sh Networking Guide
Related Guides
- Deploying Courier MTA - Alternative mail transfer agent
- Deploying EmailRelay - SMTP store-and-forward server
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.