A lightweight SMTP server that forwards incoming emails to Rails Action Mailbox webhooks with built-in spam filtering.
- Simple SMTP server that listens on port 25
- Forwards emails in RFC822 format to Action Mailbox
- Built-in spam filtering (enabled by default):
- DNS Blacklist (DNSBL) checking at connection time
- SpamAssassin integration for content analysis
- Configurable spam thresholds
- Supports basic authentication for webhooks
- Docker-ready with proper signal handling
- Pull the latest image:
docker pull ghcr.io/cmer/actionsmtp:latest- Create a configuration file:
# Copy the example configuration
cp config.example.yml config.yml
# Edit config.yml with your webhook settings- Run with Docker:
docker run -d \
--name actionsmtp \
-p 25:25 \
-v $(pwd)/config.yml:/app/config.yml:ro \
ghcr.io/cmer/actionsmtp:latest- Copy the example files:
cp config.example.yml config.yml
cp docker-compose.yml.example docker-compose.yml-
Edit
config.ymlwith your webhook URL and authentication settings -
Start the services:
docker-compose up -d- Clone the repository:
git clone https://github.com/cmer/actionsmtp.git
cd actionsmtp- Copy the example files:
cp config.example.yml config.yml
cp docker-compose.yml.example docker-compose.yml- Edit
config.ymlwith your webhook URL and authentication:
# Edit config.yml with your settings
webhooks:
yourdomain.com:
url: "http://host.docker.internal:3000/rails/action_mailbox/relay/inbound_emails"
auth:
user: "actionmailbox"
pass: "your-secret-password"- Start with Docker Compose:
docker-compose up -dActionSMTP uses a YAML configuration file (config.yml). See config.example.yml for all available options.
ActionSMTP supports routing emails to different webhooks based on the recipient domain. This allows you to:
- Run a single SMTP server for multiple applications
- Route different domains to different Rails apps or webhook endpoints
- Use different authentication credentials per domain
- Implement domain whitelisting (only configured domains are accepted)
webhooks:
# Route main domains to production
example.com, example.org:
url: "https://app.example.com/rails/action_mailbox/relay/inbound_emails"
auth:
user: "actionmailbox"
pass: "production-secret"
# Route all subdomains to staging
"*.staging.example.com":
url: "https://staging.example.com/rails/action_mailbox/relay/inbound_emails"
auth:
user: "actionmailbox"
pass: "staging-secret"
# Different app for support emails
support.example.com:
url: "https://support.example.com/webhook"
# Catch-all (not recommended - accepts any domain)
# "*":
# url: "https://default.example.com/webhook"- Exact match:
example.commatches onlyexample.com - Multiple domains:
example.com, example.orgmatches either domain - Subdomain wildcard:
*.example.commatchesexample.com,sub.example.com,deep.sub.example.com - Catch-all:
*matches any domain (use with extreme caution)
Domains are matched in the order they appear in the configuration. The first match wins.
See config.multi-domain.example.yml for a comprehensive example.
| Option | Description |
|---|---|
--config |
Path to configuration file (default: config.yml) |
--verbose |
Enable verbose logging (overrides config file) |
--help |
Show help message |
Spam filtering is enabled by default with a balanced approach:
-
DNSBL Check (at connection time):
- Checks against Spamhaus ZEN and SpamCop
- Immediate rejection of blacklisted IPs
- Fast with 3-second timeout
-
SpamAssassin Analysis (during data transfer):
- Content and header analysis
- Default threshold: 5.0 (flag as spam)
- Reject threshold: 10.0 (reject message)
- Adds X-Spam-* headers to emails
Edit your config.yml file:
# More aggressive: flag at 3.0, reject at 7.0
spam:
thresholds:
flag: 3.0
reject: 7.0
# More permissive: flag at 7.0, reject at 15.0
spam:
thresholds:
flag: 7.0
reject: 15.0
# Flag only, never reject
spam:
thresholds:
flag: 5.0
reject: 999X-Spam-Score: 7.5
X-Spam-Status: Yes, score=7.5 required=5.0
X-Spam-Tests: BAYES_99, HTML_MESSAGE, MISSING_DATE
X-Spam-DNSBL: Listed on zen.spamhaus.org
ActionSMTP provides comprehensive logging for monitoring email processing:
Shows key events with timestamps:
- Connection attempts and results
- Email reception and processing
- Spam filtering results
- Webhook delivery status
- Errors and warnings
[2024-01-01T12:00:00.000Z] INFO: CONNECT - IP: 192.168.1.100
[2024-01-01T12:00:01.000Z] INFO: EMAIL_RECEIVED - From: sender@example.com, To: recipient@example.com, IP: 192.168.1.100, Size: 1024 bytes
[2024-01-01T12:00:02.000Z] INFO: SPAM_CHECK - CLEAN - Score: 2.1/5.0, From: sender@example.com, IP: 192.168.1.100
[2024-01-01T12:00:03.000Z] INFO: WEBHOOK_SUCCESS - HTTP 200, From: sender@example.com, To: recipient@example.com
[2024-01-01T12:00:04.000Z] INFO: EMAIL_ACCEPTED - From: sender@example.com, To: recipient@example.com, IP: 192.168.1.100, Successfully processed and forwarded
Enable verbose logging to see detailed debugging information:
- SMTP command details
- SpamAssassin connection status
- DNSBL check results
- Webhook request details
- Processing timings
# In config.yml
logging:
verbose: trueOr override via command line:
actionsmtp --verbose
# or
node src/index.js --verbose- Configure Action Mailbox in your Rails app:
# config/environments/production.rb
config.action_mailbox.ingress = :relay- Set up ingress credentials:
# Generate a secure password
bin/rails runner "puts SecureRandom.base58(24)"
# Or edit your credentials file
bin/rails credentials:edit --environment=productionAdd to your credentials:
action_mailbox:
ingress_password: your-generated-password- Use this password in your ActionSMTP
config.ymlfile under the webhook auth section.
# app/mailboxes/application_mailbox.rb
class ApplicationMailbox < ActionMailbox::Base
before_processing :check_spam_headers
private
def check_spam_headers
spam_score = mail.header['X-Spam-Score']&.value&.to_f || 0
if spam_score >= 5.0
# Handle spam - quarantine, tag, or process differently
mail['X-Quarantine'] = 'spam'
end
end
end- This server accepts all incoming SMTP connections (except blacklisted IPs)
- Use firewall rules to restrict access to trusted sources
- Always use HTTPS and authentication when forwarding to production webhooks
- Consider running behind a reverse proxy for additional security
View logs:
docker-compose logs -f actionsmtpTest SMTP connection:
telnet localhost 25Check if services are running:
docker-compose psCommon issues:
- Port 25 already in use: Stop other mail servers or change the port in
config.yml - Connection refused: Ensure Docker is running and ports are properly mapped
- 401 Unauthorized: Check webhook auth credentials in
config.ymlmatch your Rails credentials - SpamAssassin connection failed: Check that the spamassassin service is running
MIT