Skip to content

Deploying EmailRelay

Introduction

E-MailRelay is a lightweight, powerful SMTP store-and-forward mail server with POP3 access to spooled messages. Built with a focus on simplicity and performance, EmailRelay provides a robust solution for email routing, filtering, and relay operations without the complexity of traditional mail servers like Postfix or Sendmail.

EmailRelay runs as a single process using a highly efficient non-blocking I/O model (similar to Nginx and Squid), offering excellent scalability and minimal resource usage. It’s ideal for personal mail servers, development environments, internal mail routing, testing SMTP workflows, and automated email processing pipelines.

Key Features:

  • Store-and-Forward Architecture: Reliable message queuing with automatic retry mechanisms
  • SMTP Server and Client: Full support for both receiving and sending email messages
  • POP3 Server: Access spooled messages via POP3 protocol with multi-user support
  • Flexible Filtering: External filter scripts for message processing, validation, and transformation
  • SpamAssassin Integration: Built-in support for spam filtering via spamd
  • DNSBL Blocklist Support: Real-time connection blocking against known spammers
  • TLS/SSL Encryption: Secure email transmission with STARTTLS and TLS connections
  • Authentication: SMTP AUTH support with PLAIN, LOGIN, CRAM-MD5, and PAM mechanisms
  • DNS MX Routing: Direct delivery to destination servers without smarthost
  • Minimal Dependencies: Easy to build and deploy with few external requirements
  • Policy-Free Design: Implement custom policies through external scripts
  • Cross-Platform: Runs on Linux, Windows, BSD, and other Unix-like systems

This comprehensive guide walks you through deploying EmailRelay on Klutch.sh using Docker, including detailed configuration, sample filter scripts, authentication setup, and production-ready best practices.

Why Deploy EmailRelay on Klutch.sh?

Deploying EmailRelay on Klutch.sh offers several advantages:

  • Automatic Deployment: Push to GitHub and your mail server deploys automatically
  • Persistent Storage: Reliable message queue storage with automatic volume management
  • Scalable Infrastructure: Handle varying email loads without manual scaling
  • TCP Port Exposure: Direct SMTP/POP3 access on port 8000 with internal routing
  • Environment Configuration: Secure credential management via environment variables
  • Zero Downtime Updates: Rolling deployments ensure continuous mail service
  • Built-in Monitoring: Track mail server performance and message throughput
  • Cost-Effective: Pay only for resources used with transparent pricing
  • SSL/TLS Ready: Built-in certificate management for secure connections
  • Simplified Operations: No server maintenance or OS updates required

Prerequisites

Before deploying EmailRelay, ensure you have:


Preparing Your Repository

Step 1: Create Your Project Directory

Start by creating a new directory for your EmailRelay deployment:

Terminal window
mkdir emailrelay-klutch
cd emailrelay-klutch
git init

Step 2: Create the Dockerfile

EmailRelay doesn’t have an official Docker image, so we’ll build a custom one. Create a Dockerfile in your project root:

Option 1: Basic Store-and-Forward Server

FROM alpine:3.19
# Install EmailRelay and dependencies
RUN apk add --no-cache \
emailrelay \
openssl \
ca-certificates \
tini
# Create spool directory
RUN mkdir -p /var/spool/emailrelay && \
chmod 750 /var/spool/emailrelay
# Create configuration directory
RUN mkdir -p /etc/emailrelay
# Expose SMTP and POP3 ports
EXPOSE 25 110
# Use tini as init system for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
# Default command: Run as store-and-forward server
CMD ["emailrelay", \
"--log", \
"--verbose", \
"--no-daemon", \
"--port", "25", \
"--spool-dir", "/var/spool/emailrelay", \
"--forward-to", "${FORWARD_TO:-smtp.gmail.com:587}", \
"--forward-on-disconnect", \
"--client-tls", \
"--client-auth", "/etc/emailrelay/auth.txt"]

Option 2: Advanced Configuration with Filters

FROM alpine:3.19
# Install EmailRelay, build tools, and dependencies
RUN apk add --no-cache \
emailrelay \
bash \
openssl \
ca-certificates \
perl \
python3 \
tini
# Create necessary directories
RUN mkdir -p /var/spool/emailrelay \
/etc/emailrelay \
/var/log/emailrelay \
/usr/local/lib/emailrelay/filters && \
chmod 750 /var/spool/emailrelay && \
chmod 755 /etc/emailrelay /var/log/emailrelay /usr/local/lib/emailrelay/filters
# Copy filter scripts (if any)
# COPY filters/ /usr/local/lib/emailrelay/filters/
# RUN chmod +x /usr/local/lib/emailrelay/filters/*.sh
# Copy custom entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Expose ports
EXPOSE 25 110 587
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD emailrelay --help > /dev/null || exit 1
# Use tini for proper signal handling
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
# Default command arguments
CMD ["--log", \
"--verbose", \
"--no-daemon", \
"--port", "25", \
"--spool-dir", "/var/spool/emailrelay"]

Option 3: Building from Source

For the latest features or custom builds:

FROM alpine:3.19 AS builder
# Install build dependencies
RUN apk add --no-cache \
build-base \
linux-headers \
openssl-dev \
perl \
automake \
autoconf \
libtool
# Download and build EmailRelay
WORKDIR /tmp
ARG EMAILRELAY_VERSION=2.5.1
RUN wget https://sourceforge.net/projects/emailrelay/files/emailrelay/${EMAILRELAY_VERSION}/emailrelay-${EMAILRELAY_VERSION}-src.tar.gz && \
tar xzf emailrelay-${EMAILRELAY_VERSION}-src.tar.gz && \
cd emailrelay-${EMAILRELAY_VERSION} && \
./configure --prefix=/usr --with-openssl && \
make && \
make DESTDIR=/tmp/install install
# Runtime image
FROM alpine:3.19
# Install runtime dependencies
RUN apk add --no-cache \
libstdc++ \
openssl \
ca-certificates \
tini
# Copy compiled binaries from builder
COPY --from=builder /tmp/install/ /
# Create directories
RUN mkdir -p /var/spool/emailrelay /etc/emailrelay /var/log/emailrelay && \
chmod 750 /var/spool/emailrelay
# Expose ports
EXPOSE 25 110 587
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD emailrelay --version || exit 1
# Use tini for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["emailrelay", \
"--log", \
"--verbose", \
"--no-daemon", \
"--port", "25", \
"--spool-dir", "/var/spool/emailrelay"]

Step 3: Create the Entrypoint Script (Optional)

For advanced configurations, create docker-entrypoint.sh:

#!/bin/bash
set -e
# EmailRelay Docker Entrypoint Script
# Handles dynamic configuration and startup
SPOOL_DIR="${SPOOL_DIR:-/var/spool/emailrelay}"
LOG_FILE="${LOG_FILE:-/var/log/emailrelay/emailrelay.log}"
PORT="${SMTP_PORT:-25}"
POP_PORT="${POP_PORT:-110}"
# Ensure directories exist with correct permissions
mkdir -p "${SPOOL_DIR}"
mkdir -p "$(dirname "${LOG_FILE}")"
chmod 750 "${SPOOL_DIR}"
# Create auth file if credentials are provided
if [ -n "${SMTP_AUTH_USER}" ] && [ -n "${SMTP_AUTH_PASS}" ]; then
echo "Creating authentication file..."
cat > /etc/emailrelay/auth.txt <<EOF
client plain ${SMTP_AUTH_USER} ${SMTP_AUTH_PASS}
EOF
chmod 600 /etc/emailrelay/auth.txt
fi
# Create server auth file for incoming connections
if [ -n "${SERVER_AUTH_FILE}" ]; then
echo "Setting up server authentication..."
echo "${SERVER_AUTH_FILE}" > /etc/emailrelay/server-auth.txt
chmod 600 /etc/emailrelay/server-auth.txt
fi
# Build EmailRelay command
EMAILRELAY_CMD="emailrelay"
# Logging options
EMAILRELAY_CMD="${EMAILRELAY_CMD} --log --verbose --no-daemon"
EMAILRELAY_CMD="${EMAILRELAY_CMD} --log-file ${LOG_FILE}"
# Spool directory
EMAILRELAY_CMD="${EMAILRELAY_CMD} --spool-dir ${SPOOL_DIR}"
# Port configuration
EMAILRELAY_CMD="${EMAILRELAY_CMD} --port ${PORT}"
# POP3 server configuration
if [ "${ENABLE_POP}" = "true" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --pop --pop-port ${POP_PORT}"
if [ -f /etc/emailrelay/pop-auth.txt ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --pop-auth /etc/emailrelay/pop-auth.txt"
fi
fi
# Forward-to configuration
if [ -n "${FORWARD_TO}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --forward-to ${FORWARD_TO}"
# Forward on disconnect or polling
if [ "${FORWARD_MODE}" = "disconnect" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --forward-on-disconnect"
elif [ -n "${POLL_INTERVAL}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --poll ${POLL_INTERVAL}"
fi
fi
# TLS configuration
if [ "${CLIENT_TLS}" = "true" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --client-tls"
fi
if [ "${SERVER_TLS}" = "true" ] && [ -f /etc/emailrelay/tls-cert.pem ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --server-tls --server-tls-certificate /etc/emailrelay/tls-cert.pem"
fi
# Authentication
if [ -f /etc/emailrelay/auth.txt ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --client-auth /etc/emailrelay/auth.txt"
fi
if [ -f /etc/emailrelay/server-auth.txt ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --server-auth /etc/emailrelay/server-auth.txt"
fi
# Remote clients
if [ "${REMOTE_CLIENTS}" = "true" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --remote-clients"
fi
# Domain configuration
if [ -n "${DOMAIN}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --domain ${DOMAIN}"
fi
# Filter script
if [ -n "${FILTER_SCRIPT}" ] && [ -f "${FILTER_SCRIPT}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --filter ${FILTER_SCRIPT}"
fi
# Address verifier
if [ -n "${ADDRESS_VERIFIER}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --address-verifier ${ADDRESS_VERIFIER}"
fi
# DNSBL configuration
if [ -n "${DNSBL_SERVERS}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --dnsbl ${DNSBL_SERVERS}"
fi
# SpamAssassin
if [ "${ENABLE_SPAM_FILTER}" = "true" ] && [ -n "${SPAMD_HOST}" ]; then
EMAILRELAY_CMD="${EMAILRELAY_CMD} --filter spam-edit:${SPAMD_HOST}:${SPAMD_PORT:-783}"
fi
echo "Starting EmailRelay with configuration:"
echo "${EMAILRELAY_CMD}"
# Execute EmailRelay (replace the shell process)
exec ${EMAILRELAY_CMD} "$@"

Make the script executable:

Terminal window
chmod +x docker-entrypoint.sh

Step 4: Create Example Filter Scripts

EmailRelay’s power comes from external filter scripts. Here are examples:

Simple Content Filter (Bash)

Create filters/content-filter.sh:

#!/bin/bash
# Simple content filter that adds a custom header
CONTENT_FILE="$1"
ENVELOPE_FILE="$2"
# Add a custom header to the message
TEMP_FILE=$(mktemp)
{
echo "X-Filtered-By: EmailRelay"
echo "X-Filter-Date: $(date -R)"
cat "${CONTENT_FILE}"
} > "${TEMP_FILE}"
# Replace original content
mv "${TEMP_FILE}" "${CONTENT_FILE}"
# Exit code 0 = success
exit 0

IP Address Validator (Bash)

Create filters/ip-validator.sh:

#!/bin/bash
# Reject messages from specific IP addresses
CONTENT_FILE="$1"
ENVELOPE_FILE="$2"
# Extract client IP from envelope
CLIENT_IP=$(grep -oP 'MailRelay-Client: \K[\d.]+' "${ENVELOPE_FILE}")
# Blocklist of IPs
BLOCKED_IPS=(
"192.168.99.99"
"10.0.0.100"
)
# Check if IP is blocked
for blocked_ip in "${BLOCKED_IPS[@]}"; do
if [ "${CLIENT_IP}" = "${blocked_ip}" ]; then
echo "<<550 Blocked IP address>>"
exit 1
fi
done
# Accept message
exit 0

Python-Based Advanced Filter

Create filters/advanced-filter.py:

#!/usr/bin/env python3
"""
Advanced EmailRelay filter script
Demonstrates content analysis and modification
"""
import sys
import email
from email.parser import Parser
from datetime import datetime
def main():
if len(sys.argv) < 3:
sys.exit(100) # Cancel processing
content_file = sys.argv[1]
envelope_file = sys.argv[2]
try:
# Read email content
with open(content_file, 'r') as f:
msg = Parser().parse(f)
# Extract envelope information
with open(envelope_file, 'r') as f:
envelope = f.read()
# Example: Check message size
content_length = sum(len(part.get_payload(decode=True) or b'')
for part in msg.walk())
if content_length > 10 * 1024 * 1024: # 10MB limit
print("<<552 Message size exceeds limit>>")
print(f"Message size: {content_length} bytes")
sys.exit(1)
# Example: Add custom headers
msg['X-Processed-By'] = 'EmailRelay-Python-Filter'
msg['X-Process-Date'] = datetime.utcnow().isoformat()
# Write modified content back
with open(content_file, 'w') as f:
f.write(msg.as_string())
# Success
sys.exit(0)
except Exception as e:
print(f"<<450 Temporary filter failure>>")
print(f"Error: {str(e)}")
sys.exit(3) # Temporary failure
if __name__ == '__main__':
main()

Make filter scripts executable:

Terminal window
chmod +x filters/*.sh filters/*.py

Step 5: Create Configuration Examples

Create emailrelay.conf for reference:

# EmailRelay Configuration File Example
# This file format is used in non-Docker deployments
# For Docker, use environment variables instead
# Logging
log
verbose
log-time
log-file /var/log/emailrelay/emailrelay.log
# Spool directory
spool-dir /var/spool/emailrelay
# SMTP Server
port 25
remote-clients
domain example.com
# TLS configuration
server-tls
server-tls-certificate /etc/emailrelay/cert.pem
client-tls
# Authentication
server-auth /etc/emailrelay/server-auth.txt
client-auth /etc/emailrelay/client-auth.txt
# Forwarding
forward-to smtp.gmail.com:587
forward-on-disconnect
# Filters
filter /usr/local/lib/emailrelay/filters/content-filter.sh
# Address verification
address-verifier account:
# POP3 Server
pop
pop-port 110
pop-auth /etc/emailrelay/pop-auth.txt
pop-by-name
# DNSBL blocking
dnsbl zen.spamhaus.org,bl.spamcop.net
# Message size limit (10MB)
size 10000000
# Connection timeout
connection-timeout 60
response-timeout 60

Step 6: Create .gitignore

Create a .gitignore file to exclude sensitive and unnecessary files:

# Secrets and credentials
*.txt
auth.txt
server-auth.txt
client-auth.txt
pop-auth.txt
*.pem
*.key
*.crt
# Logs
*.log
logs/
# Spool directory
spool/
# Docker
.dockerignore
# OS files
.DS_Store
Thumbs.db
# Editor files
*.swp
*.swo
*~
.vscode/
.idea/

Step 7: Create README.md

Document your deployment:

# EmailRelay Deployment on Klutch.sh
Store-and-forward SMTP mail server with advanced filtering and routing capabilities.
## Configuration
Set the following environment variables in Klutch.sh dashboard:
### Required
- `FORWARD_TO` - Smarthost address (e.g., smtp.gmail.com:587)
- `SMTP_AUTH_USER` - SMTP authentication username
- `SMTP_AUTH_PASS` - SMTP authentication password
### Optional
- `DOMAIN` - Your email domain (default: example.com)
- `SMTP_PORT` - SMTP server port (default: 25)
- `POP_PORT` - POP3 server port (default: 110)
- `ENABLE_POP` - Enable POP3 server (default: false)
- `REMOTE_CLIENTS` - Allow remote connections (default: false)
- `CLIENT_TLS` - Enable TLS for outgoing connections (default: true)
- `SERVER_TLS` - Enable TLS for incoming connections (default: false)
- `FORWARD_MODE` - "disconnect" or set POLL_INTERVAL
- `POLL_INTERVAL` - Polling interval in seconds (default: 60)
- `FILTER_SCRIPT` - Path to filter script
- `ADDRESS_VERIFIER` - Address verification method (e.g., account:)
- `DNSBL_SERVERS` - Comma-separated DNSBL servers
- `ENABLE_SPAM_FILTER` - Enable SpamAssassin (default: false)
- `SPAMD_HOST` - SpamAssassin daemon host
- `SPAMD_PORT` - SpamAssassin daemon port (default: 783)
## Persistent Volumes
Attach a volume at `/var/spool/emailrelay` with recommended size: 10GB
## Usage
### Testing SMTP Server
```bash
telnet your-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

Accessing Logs

Terminal window
docker exec -it container_name tail -f /var/log/emailrelay/emailrelay.log

Filter Development

Place filter scripts in /usr/local/lib/emailrelay/filters/ and make them executable.

Filter exit codes:

  • 0: Success, accept message
  • 1-99: Reject message with error
  • 100: Cancel processing (delete message)
  • 103: Trigger spool rescan

Production Checklist

  • Configure TLS certificates
  • Set up authentication
  • Configure persistent volume
  • Enable DNSBL blocking
  • Set up monitoring
  • Configure backup strategy
  • Test filter scripts
  • Set message size limits
  • Configure log rotation

Resources

### Step 8: Initialize Git and Push to GitHub
```bash
# Initialize git repository
git init
# Add all files
git add .
# Commit
git commit -m "Initial EmailRelay deployment configuration"
# Add remote repository (replace with your repo URL)
git remote add origin https://github.com/yourusername/emailrelay-klutch.git
# Push to GitHub
git branch -M main
git push -u origin main

Deploying on Klutch.sh

Now that your repository is ready, let’s deploy EmailRelay on Klutch.sh:

  1. Navigate to Klutch.sh Dashboard

    Go to klutch.sh/app and sign in to your account.

  2. Create a New Project

    • Click on “New Project”
    • Select your GitHub repository containing the EmailRelay configuration
    • Choose the repository from the dropdown
  3. Configure Deployment Settings

    Klutch.sh will automatically detect your Dockerfile. Configure the following:

    Traffic Type:

    • Select TCP traffic
    • Set external port: 8000
    • Set internal port: 25 (or your configured SMTP_PORT)
  4. Configure Environment Variables

    Add the following environment variables in the Klutch.sh dashboard:

    Required Variables:

    Terminal window
    FORWARD_TO=smtp.gmail.com:587
    SMTP_AUTH_USER=your-email@gmail.com
    SMTP_AUTH_PASS=your-app-password

    Recommended Variables:

    Terminal window
    DOMAIN=yourdomain.com
    REMOTE_CLIENTS=true
    CLIENT_TLS=true
    FORWARD_MODE=disconnect
    POLL_INTERVAL=60

    Optional Security Variables:

    Terminal window
    DNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.net
    ADDRESS_VERIFIER=account:

    POP3 Configuration (if needed):

    Terminal window
    ENABLE_POP=true
    POP_PORT=110
  5. Attach Persistent Volume

    For reliable message queuing, attach a persistent volume:

    • Click “Add Volume”
    • Mount path: /var/spool/emailrelay
    • Recommended size: 10GB (adjust based on expected mail volume)
  6. Deploy the Application

    • Click “Deploy”
    • Klutch.sh will build your Docker image and start the EmailRelay server
    • Monitor the deployment logs for any errors
  7. Verify Deployment

    Once deployed, your EmailRelay server will be accessible at:

    • SMTP Server: your-app-name.klutch.sh:8000

    Check the logs to ensure EmailRelay started successfully.


Configuration

Environment Variables Reference

VariableDescriptionDefaultRequired
FORWARD_TOSmarthost address for forwarding mailsmtp.gmail.com:587Yes
SMTP_AUTH_USERSMTP authentication username-Yes
SMTP_AUTH_PASSSMTP authentication password-Yes
DOMAINEmail domain nameexample.comNo
SMTP_PORTSMTP server listening port25No
POP_PORTPOP3 server listening port110No
ENABLE_POPEnable POP3 serverfalseNo
REMOTE_CLIENTSAllow remote connectionsfalseNo
CLIENT_TLSEnable TLS for outgoing connectionstrueNo
SERVER_TLSEnable TLS for incoming connectionsfalseNo
FORWARD_MODEForwarding mode: disconnect or use POLL_INTERVALdisconnectNo
POLL_INTERVALPolling interval in seconds60No
FILTER_SCRIPTPath to message filter script-No
ADDRESS_VERIFIERAddress verification method-No
DNSBL_SERVERSComma-separated DNSBL blocklist servers-No
ENABLE_SPAM_FILTEREnable SpamAssassin integrationfalseNo
SPAMD_HOSTSpamAssassin daemon host127.0.0.1No
SPAMD_PORTSpamAssassin daemon port783No
LOG_FILELog file path/var/log/emailrelay/emailrelay.logNo
SPOOL_DIRMessage spool directory/var/spool/emailrelayNo

Port Configuration

Since Klutch.sh uses TCP traffic mode for mail servers:

  • External Port: 8000 (accessed by clients)
  • Internal Port: 25 (where EmailRelay listens inside container)
  • Additional Ports: Configure POP3 (110) or submission port (587) as needed

Authentication Setup

EmailRelay supports multiple authentication mechanisms:

Client Authentication (Outgoing Mail)

For authenticating to your smarthost:

Terminal window
# Environment variables (recommended)
SMTP_AUTH_USER=your-email@gmail.com
SMTP_AUTH_PASS=your-app-password
CLIENT_TLS=true

The entrypoint script creates /etc/emailrelay/auth.txt:

client plain your-email@gmail.com your-app-password

Server Authentication (Incoming Mail)

To require authentication for clients connecting to your EmailRelay server:

Terminal window
# Set via environment variable
SERVER_AUTH_FILE="server plain user1 password1
server plain user2 password2"

Authentication File Format

# Format: role type username password [selector]
client plain myuser@example.com mypassword
server plain alice alicepass
server plain bob bobpass

Supported authentication types:

  • plain - Plain text passwords
  • md5 - CRAM-MD5 hashed passwords
  • oauth - OAuth2 tokens (client-side only)

TLS/SSL Configuration

Client TLS (Outgoing Connections)

Enable TLS for forwarding to smarthost:

Terminal window
CLIENT_TLS=true

Server TLS (Incoming Connections)

To enable TLS for incoming connections:

  1. Mount your TLS certificate:
Terminal window
# In Dockerfile or mount via volume
COPY tls-cert.pem /etc/emailrelay/tls-cert.pem
  1. Enable server TLS:
Terminal window
SERVER_TLS=true

The certificate file should contain both the private key and certificate chain in PEM format.

Generating Self-Signed Certificate

For testing:

Terminal window
openssl req -x509 -noenc -subj "/CN=mail.example.com" \
-newkey rsa:2048 \
-keyout emailrelay.pem \
-out emailrelay.pem \
-days 365

Filter Script Configuration

Configure message filtering via environment variable:

Terminal window
FILTER_SCRIPT=/usr/local/lib/emailrelay/filters/content-filter.sh

Filter Exit Codes:

Exit CodeAction
0Accept message
1-99Reject message with error
100Cancel processing (delete message)
103Trigger spool directory rescan

Filter Script Arguments:

  1. Content file path (message body)
  2. Envelope file path (SMTP envelope)

Address Verification

Configure address verification to validate recipients:

Terminal window
# Built-in account verifier
ADDRESS_VERIFIER=account:
# Custom script
ADDRESS_VERIFIER=/usr/local/lib/emailrelay/filters/address-verifier.sh

Built-in verifiers:

  • account: - Verify against local system accounts
  • account:check - Check local/remote without rejection
  • account:remote - Mark local recipients as remote

DNSBL Blocklist Configuration

Block connections from known spammers:

Terminal window
DNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.net,1,5000

Format: server1,server2,...,threshold,timeout_ms

  • Threshold: Number of lists required for blocking (default: 1)
  • Timeout: Milliseconds to wait for response (default: 5000)

SpamAssassin Integration

Enable spam filtering with SpamAssassin:

Terminal window
ENABLE_SPAM_FILTER=true
SPAMD_HOST=spamd-server.example.com
SPAMD_PORT=783

This uses the spam-edit: filter which modifies spam messages rather than rejecting them.


Testing EmailRelay

Testing SMTP Server with Telnet

Test basic SMTP functionality:

Terminal window
telnet your-app-name.klutch.sh 8000
# Expected response:
220 your-app-name.klutch.sh E-MailRelay ESMTP
# Test commands:
EHLO test.local
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
DATA
Subject: Test Email
This is a test message.
.
QUIT

Testing with swaks (Swiss Army Knife for SMTP)

Terminal window
# Install swaks
sudo apt-get install swaks # Ubuntu/Debian
brew install swaks # macOS
# Send test email
swaks --to recipient@example.com \
--from sender@example.com \
--server your-app-name.klutch.sh:8000 \
--auth-user your-email@gmail.com \
--auth-password your-app-password \
--tls
# Test with authentication
swaks --to recipient@example.com \
--from sender@example.com \
--server your-app-name.klutch.sh:8000 \
--auth PLAIN \
--auth-user testuser \
--auth-password testpass
# Test with TLS
swaks --to recipient@example.com \
--from sender@example.com \
--server your-app-name.klutch.sh:8000 \
--tls-on-connect

Testing POP3 Server

If POP3 is enabled:

Terminal window
telnet your-app-name.klutch.sh 8000
# Expected response:
+OK E-MailRelay POP3 server ready
# Test commands:
USER testuser
PASS testpassword
STAT
LIST
RETR 1
QUIT

Testing with Mail Clients

Python Script

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_email():
sender = "sender@example.com"
recipient = "recipient@example.com"
# Create message
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = "Test from EmailRelay"
body = "This is a test email sent through EmailRelay"
msg.attach(MIMEText(body, 'plain'))
# Connect to EmailRelay
try:
server = smtplib.SMTP('your-app-name.klutch.sh', 8000)
server.ehlo()
server.starttls() # Enable TLS
server.ehlo()
# Authenticate if required
# server.login('username', 'password')
# Send email
server.send_message(msg)
server.quit()
print("Email sent successfully!")
except Exception as e:
print(f"Error: {e}")
if __name__ == '__main__':
send_email()

Node.js Script

const nodemailer = require('nodemailer');
async function sendEmail() {
// Create transporter
const transporter = nodemailer.createTransport({
host: 'your-app-name.klutch.sh',
port: 8000,
secure: false, // Use STARTTLS
auth: {
user: 'testuser',
pass: 'testpassword'
},
tls: {
rejectUnauthorized: false // For self-signed certs
}
});
// Email options
const mailOptions = {
from: 'sender@example.com',
to: 'recipient@example.com',
subject: 'Test from EmailRelay',
text: 'This is a test email sent through EmailRelay',
html: '<p>This is a test email sent through <strong>EmailRelay</strong></p>'
};
// Send email
try {
const info = await transporter.sendMail(mailOptions);
console.log('Email sent:', info.messageId);
} catch (error) {
console.error('Error:', error);
}
}
sendEmail();

Go Script

package main
import (
"fmt"
"net/smtp"
)
func main() {
from := "sender@example.com"
to := []string{"recipient@example.com"}
// Message
msg := []byte("From: sender@example.com\r\n" +
"To: recipient@example.com\r\n" +
"Subject: Test from EmailRelay\r\n" +
"\r\n" +
"This is a test email sent through EmailRelay\r\n")
// Authentication
auth := smtp.PlainAuth("", "testuser", "testpassword", "your-app-name.klutch.sh")
// Send email
err := smtp.SendMail(
"your-app-name.klutch.sh:8000",
auth,
from,
to,
msg,
)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Email sent successfully!")
}

Ruby Script

require 'net/smtp'
def send_email
message = <<~MESSAGE
From: sender@example.com
To: recipient@example.com
Subject: Test from EmailRelay
This is a test email sent through EmailRelay
MESSAGE
begin
Net::SMTP.start(
'your-app-name.klutch.sh',
8000,
'localhost',
'testuser',
'testpassword',
:plain
) do |smtp|
smtp.send_message(
message,
'sender@example.com',
'recipient@example.com'
)
end
puts 'Email sent successfully!'
rescue => e
puts "Error: #{e.message}"
end
end
send_email

Monitoring Message Queue

Check spooled messages:

Terminal window
# List messages in spool directory
docker exec -it container_name ls -la /var/spool/emailrelay
# View envelope file
docker exec -it container_name cat /var/spool/emailrelay/emailrelay.*.envelope
# View message content
docker exec -it container_name cat /var/spool/emailrelay/emailrelay.*.content
# Check for failed messages (.bad suffix)
docker exec -it container_name ls -la /var/spool/emailrelay/*.bad

Production Best Practices

Security Configuration

1. Enable Authentication

Always require authentication for incoming connections:

Terminal window
SERVER_AUTH_FILE="server plain user1 secure-password-1
server plain user2 secure-password-2"

2. Use Strong Passwords

Generate strong passwords:

Terminal window
# Generate random password
openssl rand -base64 32

3. Enable TLS/SSL

Always use TLS for both incoming and outgoing connections:

Terminal window
CLIENT_TLS=true
SERVER_TLS=true

4. Restrict Remote Access

Only enable remote clients if necessary:

Terminal window
REMOTE_CLIENTS=true
DNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.net

5. Implement Rate Limiting

Use filter scripts to implement rate limiting:

#!/bin/bash
# Rate limit: max 100 messages per hour per IP
# Implementation left as exercise

6. Set Message Size Limits

Prevent abuse with size limits:

Add to Dockerfile CMD:

Terminal window
--size 10000000 # 10MB limit

Monitoring and Logging

1. Enable Verbose Logging

Terminal window
# Always use verbose logging in production
--log --verbose --log-time

2. Log Rotation

Implement log rotation to prevent disk space issues:

Terminal window
# Create logrotate configuration
cat > /etc/logrotate.d/emailrelay <<EOF
/var/log/emailrelay/*.log {
daily
rotate 14
compress
delaycompress
notifempty
create 640 emailrelay emailrelay
sharedscripts
postrotate
# Signal EmailRelay to reopen log files
pkill -HUP emailrelay
endscript
}
EOF

3. Monitor Spool Directory

Set up alerts for spool directory size:

#!/bin/bash
# Check spool directory size
SPOOL_SIZE=$(du -sm /var/spool/emailrelay | cut -f1)
THRESHOLD=1000 # 1GB
if [ $SPOOL_SIZE -gt $THRESHOLD ]; then
echo "WARNING: Spool directory size ${SPOOL_SIZE}MB exceeds threshold"
# Send alert
fi

4. Monitor Failed Messages

Check for .bad files regularly:

#!/bin/bash
# Count failed messages
BAD_COUNT=$(ls /var/spool/emailrelay/*.bad 2>/dev/null | wc -l)
if [ $BAD_COUNT -gt 0 ]; then
echo "WARNING: ${BAD_COUNT} failed messages in spool"
# Send alert
fi

Backup Strategy

1. Backup Spool Directory

Regular backups of message queue:

#!/bin/bash
# Backup spool directory
BACKUP_DIR="/backups/emailrelay"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "${BACKUP_DIR}"
tar czf "${BACKUP_DIR}/spool-${DATE}.tar.gz" /var/spool/emailrelay
# Keep only last 7 days
find "${BACKUP_DIR}" -name "spool-*.tar.gz" -mtime +7 -delete

2. Backup Configuration

#!/bin/bash
# Backup configuration files
BACKUP_DIR="/backups/emailrelay"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "${BACKUP_DIR}"
tar czf "${BACKUP_DIR}/config-${DATE}.tar.gz" \
/etc/emailrelay \
/usr/local/lib/emailrelay/filters
# Keep only last 30 days
find "${BACKUP_DIR}" -name "config-*.tar.gz" -mtime +30 -delete

3. Automated Backups

Set up cron job:

Terminal window
# Add to crontab
0 2 * * * /usr/local/bin/backup-emailrelay.sh

Performance Optimization

1. Connection Pooling

Configure timeout values for optimal performance:

Terminal window
--connection-timeout 60
--response-timeout 60
--idle-timeout 300

2. Polling Optimization

Adjust polling interval based on load:

Terminal window
# Low traffic: longer polling
POLL_INTERVAL=300 # 5 minutes
# High traffic: shorter polling
POLL_INTERVAL=10 # 10 seconds
# Or use forward-on-disconnect for immediate delivery
FORWARD_MODE=disconnect

3. Filter Performance

Optimize filter scripts:

#!/bin/bash
# Use efficient commands
# Avoid expensive operations in filters
# Cache frequently accessed data

4. Spool Directory Cleanup

Regularly clean up old messages:

#!/bin/bash
# Clean up messages older than 7 days
find /var/spool/emailrelay -name "emailrelay.*.envelope" -mtime +7 -delete
find /var/spool/emailrelay -name "emailrelay.*.content" -mtime +7 -delete

High Availability Setup

1. Multiple Instances

Deploy multiple EmailRelay instances behind load balancer.

2. Shared Spool Directory

Use network storage for shared spool:

Terminal window
# Mount NFS or cloud storage
mount -t nfs server:/export/spool /var/spool/emailrelay

3. Database-Backed Queue

For enterprise deployments, consider external queue management.

Disaster Recovery

1. Document Configuration

Maintain detailed documentation of your setup:

# EmailRelay Configuration Documentation
## Environment Variables
- FORWARD_TO: smtp.example.com:587
- DOMAIN: mail.example.com
- ...
## Filter Scripts
- content-filter.sh: Adds custom headers
- ip-validator.sh: Blocks specific IPs
## Volumes
- /var/spool/emailrelay: 10GB persistent storage
## Monitoring
- Spool directory size alerts
- Failed message count alerts
- Log rotation configured

2. Test Recovery Procedures

Regularly test backup restoration:

Terminal window
# Test backup restoration
tar xzf spool-backup.tar.gz -C /tmp/test-restore
# Verify contents

3. Maintain Runbook

Create operational runbook for common scenarios:

# EmailRelay Operational Runbook
## Service Down
1. Check container status
2. Review logs
3. Verify network connectivity
4. Check persistent volume
## Message Queue Backup
1. Stop service
2. Backup spool directory
3. Clear old messages
4. Restart service
## Configuration Changes
1. Update environment variables
2. Redeploy application
3. Verify functionality
4. Monitor logs

Troubleshooting

Common Issues and Solutions

1. Connection Refused

Problem: Cannot connect to EmailRelay server

Solutions:

  • Verify TCP traffic mode is enabled in Klutch.sh
  • Check external port is 8000
  • Verify internal port matches SMTP_PORT (default 25)
  • Check firewall rules
  • Review EmailRelay logs for binding errors
Terminal window
# Check if EmailRelay is listening
docker exec -it container_name netstat -tlnp | grep emailrelay

2. Authentication Failures

Problem: SMTP AUTH fails with 535 error

Solutions:

  • Verify credentials are correct
  • Check auth.txt file exists and has correct format
  • Ensure CLIENT_TLS is enabled for Gmail
  • For Gmail, use App Password, not regular password
Terminal window
# Verify auth file
docker exec -it container_name cat /etc/emailrelay/auth.txt

3. Messages Not Forwarding

Problem: Messages stuck in spool directory

Solutions:

  • Check FORWARD_TO is configured correctly
  • Verify network connectivity to smarthost
  • Review failed messages (.bad files)
  • Check authentication with smarthost
Terminal window
# List failed messages
docker exec -it container_name ls -la /var/spool/emailrelay/*.bad
# View failure reason
docker exec -it container_name cat /var/spool/emailrelay/emailrelay.*.envelope.bad

4. TLS/SSL Errors

Problem: TLS handshake failures

Solutions:

  • Verify certificate is valid
  • Check certificate path is correct
  • Ensure certificate has proper permissions (600)
  • For self-signed certs, client must trust them
Terminal window
# Test TLS connection
openssl s_client -connect your-app-name.klutch.sh:8000 -starttls smtp
# Verify certificate
openssl x509 -in /etc/emailrelay/cert.pem -text -noout

5. Filter Script Errors

Problem: Filter scripts failing or not executing

Solutions:

  • Verify script has executable permissions
  • Check script path is correct
  • Review script output in logs
  • Test script manually with sample files
Terminal window
# Make script executable
chmod +x /usr/local/lib/emailrelay/filters/filter.sh
# Test manually
/usr/local/lib/emailrelay/filters/filter.sh /tmp/content.txt /tmp/envelope.txt

6. Disk Space Issues

Problem: Spool directory filling up

Solutions:

  • Increase persistent volume size
  • Clean up old messages
  • Enable automatic cleanup
  • Monitor disk usage
Terminal window
# Check disk usage
docker exec -it container_name df -h /var/spool/emailrelay
# Count messages
docker exec -it container_name ls /var/spool/emailrelay/*.envelope | wc -l
# Clean up old messages
find /var/spool/emailrelay -name "*.envelope" -mtime +7 -delete

7. Permission Denied Errors

Problem: Cannot write to spool directory

Solutions:

  • Check directory permissions
  • Verify volume is mounted correctly
  • Ensure user has write access
Terminal window
# Fix permissions
docker exec -it container_name chmod 750 /var/spool/emailrelay
docker exec -it container_name chown -R emailrelay:emailrelay /var/spool/emailrelay

8. High Memory Usage

Problem: Container using excessive memory

Solutions:

  • Check for memory leaks
  • Limit message size
  • Reduce polling frequency
  • Optimize filter scripts
Terminal window
# Monitor memory usage
docker stats container_name
# Set memory limit in Klutch.sh
# Resource limits: 512MB - 1GB recommended

Debugging Commands

View Logs

Terminal window
# Real-time log following
docker logs -f container_name
# Last 100 lines
docker logs --tail 100 container_name
# Logs from last hour
docker logs --since 1h container_name

Check Configuration

Terminal window
# View environment variables
docker exec -it container_name env | grep -E "SMTP|FORWARD|EMAIL"
# Check running process
docker exec -it container_name ps aux | grep emailrelay
# View EmailRelay version
docker exec -it container_name emailrelay --version

Network Diagnostics

Terminal window
# Test connectivity to smarthost
docker exec -it container_name nc -zv smtp.gmail.com 587
# DNS resolution
docker exec -it container_name nslookup smtp.gmail.com
# Trace route
docker exec -it container_name traceroute smtp.gmail.com

Spool Directory Analysis

Terminal window
# Count messages by status
echo "New messages:"
docker exec -it container_name ls /var/spool/emailrelay/*.envelope.new 2>/dev/null | wc -l
echo "Busy messages:"
docker exec -it container_name ls /var/spool/emailrelay/*.envelope.busy 2>/dev/null | wc -l
echo "Failed messages:"
docker exec -it container_name ls /var/spool/emailrelay/*.envelope.bad 2>/dev/null | wc -l
# View oldest message
docker exec -it container_name ls -lt /var/spool/emailrelay/*.envelope | tail -1

Advanced Configuration

DNS MX Routing

For direct delivery without smarthost:

# Add to CMD in Dockerfile
CMD ["emailrelay", \
"--log", \
"--verbose", \
"--no-daemon", \
"--port", "25", \
"--spool-dir", "/var/spool/emailrelay", \
"--filter", "split:", \
"--client-filter", "mx:", \
"--forward-to", "127.0.0.1:9999"] # Dummy address

The split: filter divides messages by recipient domain, and mx: filter performs DNS MX lookups.

Multi-Instance Configuration

Run multiple EmailRelay instances in one container:

# emailrelay.conf for multi-instance setup
# Instance 1: Incoming mail
in-spool-dir /var/spool/emailrelay/in
in-port 25
in-remote-clients
in-domain example.com
in-filter deliver:
in-server-auth /etc/emailrelay/server-auth.txt
# Instance 2: Outgoing mail
out-spool-dir /var/spool/emailrelay/out
out-port 587
out-forward-to smtp.gmail.com:587
out-forward-on-disconnect
out-client-tls
out-client-auth /etc/emailrelay/client-auth.txt
# Instance 3: Internal routing
relay-spool-dir /var/spool/emailrelay/relay
relay-port 2525
relay-interface 127.0.0.1
relay-forward-to 127.0.0.1:587

Integration with SpamAssassin

Deploy SpamAssassin alongside EmailRelay:

# docker-compose.yml (for local development)
version: '3.8'
services:
spamassassin:
image: hanzel/spamassassin
ports:
- "783:783"
volumes:
- spamassassin-data:/var/lib/spamassassin
emailrelay:
build: .
ports:
- "2525:25"
- "110:110"
environment:
- ENABLE_SPAM_FILTER=true
- SPAMD_HOST=spamassassin
- SPAMD_PORT=783
volumes:
- emailrelay-spool:/var/spool/emailrelay
depends_on:
- spamassassin
volumes:
spamassassin-data:
emailrelay-spool:

Custom Address Verifier

Create advanced address verification:

#!/usr/bin/env python3
"""
Advanced address verifier for EmailRelay
Checks against database of valid users
"""
import sys
import sqlite3
def main():
if len(sys.argv) < 7:
sys.exit(1)
recipient = sys.argv[1]
sender = sys.argv[2]
client_ip = sys.argv[3]
domain = sys.argv[4]
auth_mech = sys.argv[5]
auth_name = sys.argv[6]
# Extract local part and domain
try:
local, recipient_domain = recipient.split('@')
except ValueError:
print("550 Invalid address format")
sys.exit(2)
# Check domain matches
if recipient_domain.lower() != domain.lower():
print("550 Relay not permitted")
sys.exit(2)
# Check against user database
try:
conn = sqlite3.connect('/var/lib/emailrelay/users.db')
cursor = conn.cursor()
cursor.execute(
"SELECT fullname, mailbox FROM users WHERE email = ? AND active = 1",
(recipient.lower(),)
)
result = cursor.fetchone()
conn.close()
if result:
# Valid local user
fullname, mailbox = result
print(f"{fullname} <{recipient}>")
print(mailbox)
sys.exit(0) # Local delivery
else:
print("550 No such user")
sys.exit(2) # Reject
except Exception as e:
print("450 Temporary verification failure")
print(str(e))
sys.exit(3) # Temporary failure
if __name__ == '__main__':
main()

POP3 with Maildir Delivery

Configure maildir delivery for POP3 access:

Terminal window
# Environment variables
ENABLE_POP=true
POP_PORT=110
FILTER_SCRIPT=/usr/local/lib/emailrelay/filters/maildir-deliver.sh

Create maildir-deliver.sh:

#!/bin/bash
# Deliver to maildir format
CONTENT_FILE="$1"
ENVELOPE_FILE="$2"
# Extract recipients marked as local
RECIPIENTS=$(grep -oP 'To-Local: \K.*' "${ENVELOPE_FILE}")
for recipient in ${RECIPIENTS}; do
MAILDIR="/var/mail/${recipient}/Maildir"
# Create maildir structure
mkdir -p "${MAILDIR}"/{new,cur,tmp}
# Generate unique filename
FILENAME="${MAILDIR}/new/$(date +%s).$(hostname).$(uuidgen)"
# Copy message
cp "${CONTENT_FILE}" "${FILENAME}"
chmod 600 "${FILENAME}"
done
# All local deliveries succeeded, delete original
if [ -n "${RECIPIENTS}" ]; then
exit 100 # Delete from spool
fi
exit 0

Administration Interface

Enable remote administration:

# Add to CMD
CMD ["emailrelay", \
..., \
"--admin", "10026", \
"--admin-terminate"]

Connect via telnet:

Terminal window
telnet your-app-name.klutch.sh 8000
# Available commands:
help # Show available commands
status # Show server status
list # List spooled messages
forward # Trigger immediate forwarding
flush # Forward with dedicated connection
unfail-all # Rename .bad files
terminate # Shutdown server (if enabled)

Additional Resources

Official Documentation

Source Code and Community

SMTP and Email Standards

Testing Tools

Security Resources


Conclusion

EmailRelay provides a powerful, lightweight solution for SMTP mail relay and store-and-forward operations. Its flexible architecture, minimal dependencies, and policy-free design make it ideal for development environments, internal mail routing, testing workflows, and automated email processing.

Deploying EmailRelay on Klutch.sh gives you automatic deployment, persistent storage, scalable infrastructure, and simplified operations. With the configuration examples, filter scripts, and best practices in this guide, you have everything needed to build a production-ready mail relay server.

Whether you’re setting up a personal mail server, building an email processing pipeline, testing SMTP workflows, or routing internal communications, EmailRelay on Klutch.sh provides a reliable and efficient foundation.

Ready to deploy? Head over to klutch.sh/app and get your EmailRelay server running in minutes!