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:
- A Klutch.sh account
- A GitHub account with a repository for your EmailRelay project
- Docker installed locally for testing (optional but recommended)
- Basic understanding of SMTP protocol and email systems
- (Optional) Domain name for email routing and DNS MX records
Preparing Your Repository
Step 1: Create Your Project Directory
Start by creating a new directory for your EmailRelay deployment:
mkdir emailrelay-klutchcd emailrelay-klutchgit initStep 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 dependenciesRUN apk add --no-cache \ emailrelay \ openssl \ ca-certificates \ tini
# Create spool directoryRUN mkdir -p /var/spool/emailrelay && \ chmod 750 /var/spool/emailrelay
# Create configuration directoryRUN mkdir -p /etc/emailrelay
# Expose SMTP and POP3 portsEXPOSE 25 110
# Use tini as init system for proper signal handlingENTRYPOINT ["/sbin/tini", "--"]
# Default command: Run as store-and-forward serverCMD ["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 dependenciesRUN apk add --no-cache \ emailrelay \ bash \ openssl \ ca-certificates \ perl \ python3 \ tini
# Create necessary directoriesRUN 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 scriptCOPY docker-entrypoint.sh /usr/local/bin/RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Expose portsEXPOSE 25 110 587
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD emailrelay --help > /dev/null || exit 1
# Use tini for proper signal handlingENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
# Default command argumentsCMD ["--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 dependenciesRUN apk add --no-cache \ build-base \ linux-headers \ openssl-dev \ perl \ automake \ autoconf \ libtool
# Download and build EmailRelayWORKDIR /tmpARG EMAILRELAY_VERSION=2.5.1RUN 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 imageFROM alpine:3.19
# Install runtime dependenciesRUN apk add --no-cache \ libstdc++ \ openssl \ ca-certificates \ tini
# Copy compiled binaries from builderCOPY --from=builder /tmp/install/ /
# Create directoriesRUN mkdir -p /var/spool/emailrelay /etc/emailrelay /var/log/emailrelay && \ chmod 750 /var/spool/emailrelay
# Expose portsEXPOSE 25 110 587
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD emailrelay --version || exit 1
# Use tini for proper signal handlingENTRYPOINT ["/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/bashset -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 permissionsmkdir -p "${SPOOL_DIR}"mkdir -p "$(dirname "${LOG_FILE}")"chmod 750 "${SPOOL_DIR}"
# Create auth file if credentials are providedif [ -n "${SMTP_AUTH_USER}" ] && [ -n "${SMTP_AUTH_PASS}" ]; then echo "Creating authentication file..." cat > /etc/emailrelay/auth.txt <<EOFclient plain ${SMTP_AUTH_USER} ${SMTP_AUTH_PASS}EOF chmod 600 /etc/emailrelay/auth.txtfi
# Create server auth file for incoming connectionsif [ -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.txtfi
# Build EmailRelay commandEMAILRELAY_CMD="emailrelay"
# Logging optionsEMAILRELAY_CMD="${EMAILRELAY_CMD} --log --verbose --no-daemon"EMAILRELAY_CMD="${EMAILRELAY_CMD} --log-file ${LOG_FILE}"
# Spool directoryEMAILRELAY_CMD="${EMAILRELAY_CMD} --spool-dir ${SPOOL_DIR}"
# Port configurationEMAILRELAY_CMD="${EMAILRELAY_CMD} --port ${PORT}"
# POP3 server configurationif [ "${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" fifi
# Forward-to configurationif [ -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}" fifi
# TLS configurationif [ "${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
# Authenticationif [ -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 clientsif [ "${REMOTE_CLIENTS}" = "true" ]; then EMAILRELAY_CMD="${EMAILRELAY_CMD} --remote-clients"fi
# Domain configurationif [ -n "${DOMAIN}" ]; then EMAILRELAY_CMD="${EMAILRELAY_CMD} --domain ${DOMAIN}"fi
# Filter scriptif [ -n "${FILTER_SCRIPT}" ] && [ -f "${FILTER_SCRIPT}" ]; then EMAILRELAY_CMD="${EMAILRELAY_CMD} --filter ${FILTER_SCRIPT}"fi
# Address verifierif [ -n "${ADDRESS_VERIFIER}" ]; then EMAILRELAY_CMD="${EMAILRELAY_CMD} --address-verifier ${ADDRESS_VERIFIER}"fi
# DNSBL configurationif [ -n "${DNSBL_SERVERS}" ]; then EMAILRELAY_CMD="${EMAILRELAY_CMD} --dnsbl ${DNSBL_SERVERS}"fi
# SpamAssassinif [ "${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:
chmod +x docker-entrypoint.shStep 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 messageTEMP_FILE=$(mktemp){ echo "X-Filtered-By: EmailRelay" echo "X-Filter-Date: $(date -R)" cat "${CONTENT_FILE}"} > "${TEMP_FILE}"
# Replace original contentmv "${TEMP_FILE}" "${CONTENT_FILE}"
# Exit code 0 = successexit 0IP 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 envelopeCLIENT_IP=$(grep -oP 'MailRelay-Client: \K[\d.]+' "${ENVELOPE_FILE}")
# Blocklist of IPsBLOCKED_IPS=( "192.168.99.99" "10.0.0.100")
# Check if IP is blockedfor blocked_ip in "${BLOCKED_IPS[@]}"; do if [ "${CLIENT_IP}" = "${blocked_ip}" ]; then echo "<<550 Blocked IP address>>" exit 1 fidone
# Accept messageexit 0Python-Based Advanced Filter
Create filters/advanced-filter.py:
#!/usr/bin/env python3"""Advanced EmailRelay filter scriptDemonstrates content analysis and modification"""import sysimport emailfrom email.parser import Parserfrom 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:
chmod +x filters/*.sh filters/*.pyStep 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
# Logginglogverboselog-timelog-file /var/log/emailrelay/emailrelay.log
# Spool directoryspool-dir /var/spool/emailrelay
# SMTP Serverport 25remote-clientsdomain example.com
# TLS configurationserver-tlsserver-tls-certificate /etc/emailrelay/cert.pemclient-tls
# Authenticationserver-auth /etc/emailrelay/server-auth.txtclient-auth /etc/emailrelay/client-auth.txt
# Forwardingforward-to smtp.gmail.com:587forward-on-disconnect
# Filtersfilter /usr/local/lib/emailrelay/filters/content-filter.sh
# Address verificationaddress-verifier account:
# POP3 Serverpoppop-port 110pop-auth /etc/emailrelay/pop-auth.txtpop-by-name
# DNSBL blockingdnsbl zen.spamhaus.org,bl.spamcop.net
# Message size limit (10MB)size 10000000
# Connection timeoutconnection-timeout 60response-timeout 60Step 6: Create .gitignore
Create a .gitignore file to exclude sensitive and unnecessary files:
# Secrets and credentials*.txtauth.txtserver-auth.txtclient-auth.txtpop-auth.txt*.pem*.key*.crt
# Logs*.loglogs/
# Spool directoryspool/
# Docker.dockerignore
# OS files.DS_StoreThumbs.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
```bashtelnet your-app.klutch.sh 8000EHLO test.localMAIL FROM:<sender@example.com>RCPT TO:<recipient@example.com>DATASubject: Test Email
This is a test message..QUITAccessing Logs
docker exec -it container_name tail -f /var/log/emailrelay/emailrelay.logFilter 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
- Official Docs: https://emailrelay.sourceforge.net/
- GitHub: https://github.com/aclemons/emailrelay
### Step 8: Initialize Git and Push to GitHub
```bash# Initialize git repositorygit init
# Add all filesgit add .
# Commitgit 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 GitHubgit branch -M maingit push -u origin mainDeploying on Klutch.sh
Now that your repository is ready, let’s deploy EmailRelay on Klutch.sh:
-
Navigate to Klutch.sh Dashboard
Go to klutch.sh/app and sign in to your account.
-
Create a New Project
- Click on “New Project”
- Select your GitHub repository containing the EmailRelay configuration
- Choose the repository from the dropdown
-
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)
-
Configure Environment Variables
Add the following environment variables in the Klutch.sh dashboard:
Required Variables:
Terminal window FORWARD_TO=smtp.gmail.com:587SMTP_AUTH_USER=your-email@gmail.comSMTP_AUTH_PASS=your-app-passwordRecommended Variables:
Terminal window DOMAIN=yourdomain.comREMOTE_CLIENTS=trueCLIENT_TLS=trueFORWARD_MODE=disconnectPOLL_INTERVAL=60Optional Security Variables:
Terminal window DNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.netADDRESS_VERIFIER=account:POP3 Configuration (if needed):
Terminal window ENABLE_POP=truePOP_PORT=110 -
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)
-
Deploy the Application
- Click “Deploy”
- Klutch.sh will build your Docker image and start the EmailRelay server
- Monitor the deployment logs for any errors
-
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.
- SMTP Server:
Configuration
Environment Variables Reference
| Variable | Description | Default | Required |
|---|---|---|---|
FORWARD_TO | Smarthost address for forwarding mail | smtp.gmail.com:587 | Yes |
SMTP_AUTH_USER | SMTP authentication username | - | Yes |
SMTP_AUTH_PASS | SMTP authentication password | - | Yes |
DOMAIN | Email domain name | example.com | No |
SMTP_PORT | SMTP server listening port | 25 | No |
POP_PORT | POP3 server listening port | 110 | No |
ENABLE_POP | Enable POP3 server | false | No |
REMOTE_CLIENTS | Allow remote connections | false | No |
CLIENT_TLS | Enable TLS for outgoing connections | true | No |
SERVER_TLS | Enable TLS for incoming connections | false | No |
FORWARD_MODE | Forwarding mode: disconnect or use POLL_INTERVAL | disconnect | No |
POLL_INTERVAL | Polling interval in seconds | 60 | No |
FILTER_SCRIPT | Path to message filter script | - | No |
ADDRESS_VERIFIER | Address verification method | - | No |
DNSBL_SERVERS | Comma-separated DNSBL blocklist servers | - | No |
ENABLE_SPAM_FILTER | Enable SpamAssassin integration | false | No |
SPAMD_HOST | SpamAssassin daemon host | 127.0.0.1 | No |
SPAMD_PORT | SpamAssassin daemon port | 783 | No |
LOG_FILE | Log file path | /var/log/emailrelay/emailrelay.log | No |
SPOOL_DIR | Message spool directory | /var/spool/emailrelay | No |
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:
# Environment variables (recommended)SMTP_AUTH_USER=your-email@gmail.comSMTP_AUTH_PASS=your-app-passwordCLIENT_TLS=trueThe entrypoint script creates /etc/emailrelay/auth.txt:
client plain your-email@gmail.com your-app-passwordServer Authentication (Incoming Mail)
To require authentication for clients connecting to your EmailRelay server:
# Set via environment variableSERVER_AUTH_FILE="server plain user1 password1server plain user2 password2"Authentication File Format
# Format: role type username password [selector]client plain myuser@example.com mypasswordserver plain alice alicepassserver plain bob bobpassSupported authentication types:
plain- Plain text passwordsmd5- CRAM-MD5 hashed passwordsoauth- OAuth2 tokens (client-side only)
TLS/SSL Configuration
Client TLS (Outgoing Connections)
Enable TLS for forwarding to smarthost:
CLIENT_TLS=trueServer TLS (Incoming Connections)
To enable TLS for incoming connections:
- Mount your TLS certificate:
# In Dockerfile or mount via volumeCOPY tls-cert.pem /etc/emailrelay/tls-cert.pem- Enable server TLS:
SERVER_TLS=trueThe certificate file should contain both the private key and certificate chain in PEM format.
Generating Self-Signed Certificate
For testing:
openssl req -x509 -noenc -subj "/CN=mail.example.com" \ -newkey rsa:2048 \ -keyout emailrelay.pem \ -out emailrelay.pem \ -days 365Filter Script Configuration
Configure message filtering via environment variable:
FILTER_SCRIPT=/usr/local/lib/emailrelay/filters/content-filter.shFilter Exit Codes:
| Exit Code | Action |
|---|---|
| 0 | Accept message |
| 1-99 | Reject message with error |
| 100 | Cancel processing (delete message) |
| 103 | Trigger spool directory rescan |
Filter Script Arguments:
- Content file path (message body)
- Envelope file path (SMTP envelope)
Address Verification
Configure address verification to validate recipients:
# Built-in account verifierADDRESS_VERIFIER=account:
# Custom scriptADDRESS_VERIFIER=/usr/local/lib/emailrelay/filters/address-verifier.shBuilt-in verifiers:
account:- Verify against local system accountsaccount:check- Check local/remote without rejectionaccount:remote- Mark local recipients as remote
DNSBL Blocklist Configuration
Block connections from known spammers:
DNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.net,1,5000Format: 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:
ENABLE_SPAM_FILTER=trueSPAMD_HOST=spamd-server.example.comSPAMD_PORT=783This uses the spam-edit: filter which modifies spam messages rather than rejecting them.
Testing EmailRelay
Testing SMTP Server with Telnet
Test basic SMTP functionality:
telnet your-app-name.klutch.sh 8000
# Expected response:220 your-app-name.klutch.sh E-MailRelay ESMTP
# Test commands:EHLO test.localMAIL FROM:<sender@example.com>RCPT TO:<recipient@example.com>DATASubject: Test Email
This is a test message..QUITTesting with swaks (Swiss Army Knife for SMTP)
# Install swakssudo apt-get install swaks # Ubuntu/Debianbrew install swaks # macOS
# Send test emailswaks --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 authenticationswaks --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 TLSswaks --to recipient@example.com \ --from sender@example.com \ --server your-app-name.klutch.sh:8000 \ --tls-on-connectTesting POP3 Server
If POP3 is enabled:
telnet your-app-name.klutch.sh 8000
# Expected response:+OK E-MailRelay POP3 server ready
# Test commands:USER testuserPASS testpasswordSTATLISTRETR 1QUITTesting with Mail Clients
Python Script
import smtplibfrom email.mime.text import MIMETextfrom 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}" endend
send_emailMonitoring Message Queue
Check spooled messages:
# List messages in spool directorydocker exec -it container_name ls -la /var/spool/emailrelay
# View envelope filedocker exec -it container_name cat /var/spool/emailrelay/emailrelay.*.envelope
# View message contentdocker 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/*.badProduction Best Practices
Security Configuration
1. Enable Authentication
Always require authentication for incoming connections:
SERVER_AUTH_FILE="server plain user1 secure-password-1server plain user2 secure-password-2"2. Use Strong Passwords
Generate strong passwords:
# Generate random passwordopenssl rand -base64 323. Enable TLS/SSL
Always use TLS for both incoming and outgoing connections:
CLIENT_TLS=trueSERVER_TLS=true4. Restrict Remote Access
Only enable remote clients if necessary:
REMOTE_CLIENTS=trueDNSBL_SERVERS=zen.spamhaus.org,bl.spamcop.net5. Implement Rate Limiting
Use filter scripts to implement rate limiting:
#!/bin/bash# Rate limit: max 100 messages per hour per IP# Implementation left as exercise6. Set Message Size Limits
Prevent abuse with size limits:
Add to Dockerfile CMD:
--size 10000000 # 10MB limitMonitoring and Logging
1. Enable Verbose Logging
# Always use verbose logging in production--log --verbose --log-time2. Log Rotation
Implement log rotation to prevent disk space issues:
# Create logrotate configurationcat > /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}EOF3. Monitor Spool Directory
Set up alerts for spool directory size:
#!/bin/bash# Check spool directory sizeSPOOL_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 alertfi4. Monitor Failed Messages
Check for .bad files regularly:
#!/bin/bash# Count failed messagesBAD_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 alertfiBackup Strategy
1. Backup Spool Directory
Regular backups of message queue:
#!/bin/bash# Backup spool directoryBACKUP_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 daysfind "${BACKUP_DIR}" -name "spool-*.tar.gz" -mtime +7 -delete2. Backup Configuration
#!/bin/bash# Backup configuration filesBACKUP_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 daysfind "${BACKUP_DIR}" -name "config-*.tar.gz" -mtime +30 -delete3. Automated Backups
Set up cron job:
# Add to crontab0 2 * * * /usr/local/bin/backup-emailrelay.shPerformance Optimization
1. Connection Pooling
Configure timeout values for optimal performance:
--connection-timeout 60--response-timeout 60--idle-timeout 3002. Polling Optimization
Adjust polling interval based on load:
# Low traffic: longer pollingPOLL_INTERVAL=300 # 5 minutes
# High traffic: shorter pollingPOLL_INTERVAL=10 # 10 seconds
# Or use forward-on-disconnect for immediate deliveryFORWARD_MODE=disconnect3. Filter Performance
Optimize filter scripts:
#!/bin/bash# Use efficient commands# Avoid expensive operations in filters# Cache frequently accessed data4. Spool Directory Cleanup
Regularly clean up old messages:
#!/bin/bash# Clean up messages older than 7 daysfind /var/spool/emailrelay -name "emailrelay.*.envelope" -mtime +7 -deletefind /var/spool/emailrelay -name "emailrelay.*.content" -mtime +7 -deleteHigh Availability Setup
1. Multiple Instances
Deploy multiple EmailRelay instances behind load balancer.
2. Shared Spool Directory
Use network storage for shared spool:
# Mount NFS or cloud storagemount -t nfs server:/export/spool /var/spool/emailrelay3. 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 configured2. Test Recovery Procedures
Regularly test backup restoration:
# Test backup restorationtar xzf spool-backup.tar.gz -C /tmp/test-restore# Verify contents3. Maintain Runbook
Create operational runbook for common scenarios:
# EmailRelay Operational Runbook
## Service Down1. Check container status2. Review logs3. Verify network connectivity4. Check persistent volume
## Message Queue Backup1. Stop service2. Backup spool directory3. Clear old messages4. Restart service
## Configuration Changes1. Update environment variables2. Redeploy application3. Verify functionality4. Monitor logsTroubleshooting
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
# Check if EmailRelay is listeningdocker exec -it container_name netstat -tlnp | grep emailrelay2. 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
# Verify auth filedocker exec -it container_name cat /etc/emailrelay/auth.txt3. 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
# List failed messagesdocker exec -it container_name ls -la /var/spool/emailrelay/*.bad
# View failure reasondocker exec -it container_name cat /var/spool/emailrelay/emailrelay.*.envelope.bad4. 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
# Test TLS connectionopenssl s_client -connect your-app-name.klutch.sh:8000 -starttls smtp
# Verify certificateopenssl x509 -in /etc/emailrelay/cert.pem -text -noout5. 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
# Make script executablechmod +x /usr/local/lib/emailrelay/filters/filter.sh
# Test manually/usr/local/lib/emailrelay/filters/filter.sh /tmp/content.txt /tmp/envelope.txt6. Disk Space Issues
Problem: Spool directory filling up
Solutions:
- Increase persistent volume size
- Clean up old messages
- Enable automatic cleanup
- Monitor disk usage
# Check disk usagedocker exec -it container_name df -h /var/spool/emailrelay
# Count messagesdocker exec -it container_name ls /var/spool/emailrelay/*.envelope | wc -l
# Clean up old messagesfind /var/spool/emailrelay -name "*.envelope" -mtime +7 -delete7. Permission Denied Errors
Problem: Cannot write to spool directory
Solutions:
- Check directory permissions
- Verify volume is mounted correctly
- Ensure user has write access
# Fix permissionsdocker exec -it container_name chmod 750 /var/spool/emailrelaydocker exec -it container_name chown -R emailrelay:emailrelay /var/spool/emailrelay8. High Memory Usage
Problem: Container using excessive memory
Solutions:
- Check for memory leaks
- Limit message size
- Reduce polling frequency
- Optimize filter scripts
# Monitor memory usagedocker stats container_name
# Set memory limit in Klutch.sh# Resource limits: 512MB - 1GB recommendedDebugging Commands
View Logs
# Real-time log followingdocker logs -f container_name
# Last 100 linesdocker logs --tail 100 container_name
# Logs from last hourdocker logs --since 1h container_nameCheck Configuration
# View environment variablesdocker exec -it container_name env | grep -E "SMTP|FORWARD|EMAIL"
# Check running processdocker exec -it container_name ps aux | grep emailrelay
# View EmailRelay versiondocker exec -it container_name emailrelay --versionNetwork Diagnostics
# Test connectivity to smarthostdocker exec -it container_name nc -zv smtp.gmail.com 587
# DNS resolutiondocker exec -it container_name nslookup smtp.gmail.com
# Trace routedocker exec -it container_name traceroute smtp.gmail.comSpool Directory Analysis
# Count messages by statusecho "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 messagedocker exec -it container_name ls -lt /var/spool/emailrelay/*.envelope | tail -1Advanced Configuration
DNS MX Routing
For direct delivery without smarthost:
# Add to CMD in DockerfileCMD ["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 addressThe 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 mailin-spool-dir /var/spool/emailrelay/inin-port 25in-remote-clientsin-domain example.comin-filter deliver:in-server-auth /etc/emailrelay/server-auth.txt
# Instance 2: Outgoing mailout-spool-dir /var/spool/emailrelay/outout-port 587out-forward-to smtp.gmail.com:587out-forward-on-disconnectout-client-tlsout-client-auth /etc/emailrelay/client-auth.txt
# Instance 3: Internal routingrelay-spool-dir /var/spool/emailrelay/relayrelay-port 2525relay-interface 127.0.0.1relay-forward-to 127.0.0.1:587Integration 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 EmailRelayChecks against database of valid users"""import sysimport 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:
# Environment variablesENABLE_POP=truePOP_PORT=110FILTER_SCRIPT=/usr/local/lib/emailrelay/filters/maildir-deliver.shCreate maildir-deliver.sh:
#!/bin/bash# Deliver to maildir format
CONTENT_FILE="$1"ENVELOPE_FILE="$2"
# Extract recipients marked as localRECIPIENTS=$(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 originalif [ -n "${RECIPIENTS}" ]; then exit 100 # Delete from spoolfi
exit 0Administration Interface
Enable remote administration:
# Add to CMDCMD ["emailrelay", \ ..., \ "--admin", "10026", \ "--admin-terminate"]Connect via telnet:
telnet your-app-name.klutch.sh 8000
# Available commands:help # Show available commandsstatus # Show server statuslist # List spooled messagesforward # Trigger immediate forwardingflush # Forward with dedicated connectionunfail-all # Rename .bad filesterminate # Shutdown server (if enabled)Additional Resources
Official Documentation
Source Code and Community
SMTP and Email Standards
- RFC 5321 - SMTP Protocol
- RFC 5322 - Internet Message Format
- RFC 3207 - SMTP STARTTLS
- RFC 4954 - SMTP AUTH
Related Guides
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!