Author: Martin Krivosija - LinkedIn
A simple, efficient script that provides an SMTP server to receive emails, parse content (including headers), store attachments in Amazon S3, and forward email content to a webhook. Graceful handling of multiple concurrent SMTP sessions and webhook requests.
- SMTP server to receive emails concurrently
- Parses incoming emails using
mailparser - Enhanced Error Recovery: Automatic fallback to local storage when S3 is unavailable, with background retry mechanism
- Multiple Webhook Support: Route emails to different webhooks based on sender, recipient, subject, or custom rules
- Uploads attachments to Amazon S3 with intelligent fallback
- Forwards parsed email content to webhook endpoints
- Configurable via environment variables
- Handles large attachments gracefully
- Robust queue system for processing multiple emails and webhook requests simultaneously
- Durable disk-backed queue for crash-safe webhook delivery
- Webhook request signing with HMAC (
X-Inbound-Email-Signature) - SMTP ingress hardening (IP allowlists, sender restrictions, rate limits, message size limits)
- Optional encrypted local attachment storage at rest (AES-256-GCM)
- Comprehensive Test Coverage: >90% code coverage with Jest testing framework
- Daily rotating logs with 90-day retention
- Node.js (v18 or later recommended)
- If saving attachments, an Amazon Web Services (AWS) account with S3 access or a compatible system
- A HTTP(s) webhook endpoint to receive the processed emails
-
Clone this repository:
git clone https://github.com/kriiv/inbound-email.git cd inbound-email -
Install dependencies:
npm install -
Copy the
.env.examplefile to.envand set the required configuration: (eg.mv .env.example .env)Variable Description Required Default WEBHOOK_URLThe URL where parsed emails will be sent (if not using rules) Yes* nullWEBHOOK_RULESJSON configuration for multiple webhook routing (see below) No nullPORTThe port for the SMTP server to listen on No 25SMTP_SECURESet to 'true' for TLS support (requires key/cert setup) No falseWEBHOOK_CONCURRENCYNumber of concurrent webhook requests No 5WEBHOOK_RETRY_DELAY_MSDelay before retrying failed durable queue tasks No 60000SMTP_MAX_CLIENTSMaximum simultaneous SMTP client connections No 200SMTP_SOCKET_TIMEOUTSocket idle timeout in milliseconds No 60000SMTP_CLOSE_TIMEOUTGraceful close wait timeout in milliseconds No 30000SMTP_MAX_MESSAGE_SIZEMaximum accepted SMTP message size in bytes No 10485760SMTP_RATE_LIMIT_WINDOW_MSWindow length for per-IP connection limiting in milliseconds No 60000SMTP_RATE_LIMIT_MAX_CONNECTIONSMax connections per IP per window No 120MAX_QUEUE_SIZEMaximum in-memory active queue items before temporary reject No 1000DURABLE_QUEUE_PATHPath for persisted webhook queue items No ./queue-dataVariable Description Required Default MAX_FILE_SIZEMaximum attachment size in bytes (0 to disable S3 uploads) No 5242880(5MB)AWS_REGIONYour AWS region If saving nullAWS_ACCESS_KEY_IDYour AWS access key ID If saving nullAWS_SECRET_ACCESS_KEYYour AWS secret access key If saving nullS3_BUCKET_NAMEThe name of your S3 bucket for storing attachments If saving nullVariable Description Required Default LOCAL_STORAGE_PATHDirectory for temporary storage when S3 fails No ./temp-attachmentsLOCAL_STORAGE_RETENTIONHours to keep local files before cleanup No 24LOCAL_STORAGE_ENCRYPTION_KEY32-byte key (hex/base64) for local AES-256-GCM encryption No nullEXPOSE_LOCAL_ATTACHMENT_PATHSInclude local disk paths in webhook payloads No falseS3_RETRY_INTERVALMinutes between S3 retry attempts No 5Variable Description Required Default REQUIRE_TRUSTED_RELAYReject SMTP clients unless they are in TRUSTED_RELAY_IPSNo falseTRUSTED_RELAY_IPSComma-separated trusted relay/source IPs If enabled []ALLOWED_SMTP_CLIENTSComma-separated allowed SMTP client/source IPs No []ALLOWED_SENDER_DOMAINSComma-separated envelope sender domain allowlist No []REQUIRED_AUTH_RESULTSRequired Authentication-Resultstokens (e.g.spf=pass)No []Variable Description Required Default WEBHOOK_SECRETHMAC secret used to sign outbound webhook payloads No nullALLOW_INSECURE_WEBHOOK_HTTPAllow non-TLS ( http://) webhook URLsNo falseVariable Description Required Default TLS_KEY_PATHPath to the TLS private key file (if SMTP_SECURE=true)If secure nullTLS_CERT_PATHPath to the TLS certificate file (if SMTP_SECURE=true)If secure nullNote: Either
WEBHOOK_URLorWEBHOOK_RULESmust be configured. S3 credentials are required ifMAX_FILE_SIZE> 0.
Start the server:
npm start
The SMTP server will start and listen on the specified port (default: 25) on all network interfaces.
You can use pm2 or supervisor to keep the server running after restart. Example: pm2 start server.js
When NODE_ENV=production, startup validation enforces hardened defaults:
REQUIRE_TRUSTED_RELAY=trueTRUSTED_RELAY_IPSmust be setALLOWED_RECIPIENT_DOMAINSmust be setWEBHOOK_SECRETmust be setALLOW_INSECURE_WEBHOOK_HTTPmust befalse
The service supports routing emails to different webhooks based on email content. Configure using the WEBHOOK_RULES environment variable:
{
"rules": [
{
"name": "support-emails",
"conditions": { "to": "support@*" },
"webhook": "https://support.webhook.com",
"priority": 1
},
{
"name": "sales-inquiries",
"conditions": { "subject": "*quote*" },
"webhook": "https://sales.webhook.com",
"priority": 2
},
{
"name": "default",
"conditions": {},
"webhook": "https://default.webhook.com",
"priority": 999
}
]
}{
"rules": [
{
"name": "urgent-admin",
"conditions": {
"from": "admin@company.com",
"subject": "/^URGENT:/i"
},
"webhook": "https://urgent.webhook.com",
"priority": 1,
"stopProcessing": true
},
{
"name": "with-attachments",
"conditions": { "hasAttachments": "true" },
"webhook": "https://attachments.webhook.com",
"priority": 5
},
{
"name": "team-notifications",
"conditions": { "to": "team-*@company.com" },
"webhook": "https://team.webhook.com",
"priority": 10
}
]
}- Exact match:
"from": "admin@company.com" - Wildcard:
"to": "support@*"or"subject": "*report*" - Regex:
"subject": "/^URGENT:/i" - Built-in fields:
from,to,cc: Email addressessubject: Email subject linehasAttachments: "true" or "false"
- Custom headers:
"header": {"name": "X-Priority", "value": "high"}
- Rules are sorted by
priority(lower = higher priority) - All matching rules receive the email (unless
stopProcessing: true) - If no rules match, falls back to
WEBHOOK_URLif configured - Each webhook receives the email data plus
_webhookMetawith rule information
The service provides automatic resilience for attachment storage:
- When S3 is unavailable, attachments are stored locally in
LOCAL_STORAGE_PATH - Background retry process attempts S3 upload every
S3_RETRY_INTERVALminutes - Files older than
LOCAL_STORAGE_RETENTIONhours are automatically cleaned up - Webhooks receive storage location and type information
{
"from": "sender@example.com",
"subject": "Email with attachments",
"attachmentInfo": [
{
"filename": "document.pdf",
"size": 1024,
"location": "https://s3.amazonaws.com/bucket/document.pdf",
"storageType": "s3"
},
{
"filename": "backup.zip",
"size": 2048,
"location": "/temp-attachments/123456-backup.zip",
"storageType": "local",
"note": "Temporarily stored locally, will be uploaded to S3 when available"
}
],
"storageSummary": {
"total": 2,
"uploadedToS3": 1,
"storedLocally": 1,
"skipped": 0
}
}Run the comprehensive test suite:
# Install dev dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode (during development)
npm run test:watchThe test suite provides >90% code coverage across all services.
-
Email to Ticket System: Use this bridge to receive support emails and automatically create tickets in your helpdesk system via the webhook.
-
Document Processing: Receive emails with document attachments, store them in S3, and trigger a document processing pipeline through the webhook.
-
Email Marketing Analysis: Collect incoming emails from a campaign, store any images or attachments, and send the content to an analytics system for processing.
-
Automated Reporting: Set up an email address that receives automated reports, stores them in S3, and notifies your team via the webhook.
-
DMARC Reporting: Receive DMARC reports via email and store them in S3.
Using inbound parse for something interesting? Please let me know, I'd love to hear about it.
- Rate limiting
Log Storage(completed)
Contributions are welcome! Please feel free to submit a Pull Request or get in touch.
This project is licensed under the MIT License. This means you are free to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software, including for commercial purposes.
Please ensure you have the necessary permissions and security measures in place when deploying an SMTP server. Depending on your firewall configuration, you may be exposing this service to the internet.
When deploying this SMTP server, please keep the following security considerations in mind:
- Ensure that your server is properly secured and that only authorized IPs can access the SMTP port.
- Use strong, unique passwords for your AWS credentials and keep them secure.
- Regularly update the Node.js runtime and all dependencies to their latest versions.
- Consider implementing additional authentication mechanisms for the SMTP server if needed.
The server logs information about received emails, webhook responses, and any errors that occur. The current logging setup includes:
- Console output for immediate visibility
- Daily rotating log files for persistent storage
- JSON formatting of log entries for easy parsing
- Timestamp inclusion for each log entry
Logging settings:
- Log files are stored in the
logs/directory - Files are named
application-YYYY-MM-DD.log - Log files are rotated daily and compressed
- Maximum log file size is set to 20MB
- Log files are kept for 90 days
I recommend:
- Review log files regularly for errors or unusual patterns.
- Consider setting up log aggregation and analysis tools (e.g., ELK stack, Splunk).
- Implement alerts for critical errors or unusual activity patterns.
- Monitor system resources (CPU, memory, disk space) to ensure smooth operation.
- Set up uptime monitoring for the SMTP server and webhook endpoint.
- Node.js v18 or later
- Sufficient disk space for temporary storage of attachments before S3 upload and 90 days of logging.
- Outbound internet access for S3 uploads and webhook calls
- Inbound access on the configured SMTP port. (Default: 25, or 587 if
SMTP_SECUREis set to 'true')
If you encounter issues:
- Check the server logs for any error messages.
- Ensure all environment variables are correctly set.
- Verify that your AWS credentials have the necessary permissions for S3 operations.
- Check that the webhook endpoint is accessible and responding correctly.
- For attachment issues, verify that the
MAX_FILE_SIZEsetting is appropriate for your use case.
If problems persist, please open an issue on the GitHub repository with detailed information about the error and your setup.