# Plunk SMTP Relay Server A production-ready SMTP relay server that accepts emails via SMTP protocol and forwards them to the Plunk API's `/v1/send` endpoint. ## Features - **Secure Authentication**: API key-based authentication using project secrets - **Domain Verification**: Validates sender domains are verified before accepting emails - **TLS Support**: Supports both implicit TLS (port 465) and STARTTLS (port 587) - **Flexible Certificate Handling**: Works with Traefik's acme.json or standard PEM files - **Email Parsing**: Full email parsing with support for HTML and plain text - **Rate Limiting**: Configurable recipient limits per email - **Production Ready**: Built with TypeScript, error handling, and logging ## Architecture ``` SMTP Client → SMTP Server → Email Parser → API /v1/send → AWS SES ``` The SMTP server acts as a relay: 1. Accepts SMTP connections with authentication 2. Validates sender domains against the database 3. Parses incoming emails 4. Forwards to the Plunk API 5. Returns success/error to the SMTP client ## Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `API_URI` | `http://localhost:3000` | Plunk API base URL | | `SMTP_DOMAIN` | *(empty)* | SMTP domain - required when using Traefik acme.json with multiple certificates | | `PORT_SECURE` | `465` | SMTPS port (implicit TLS) | | `PORT_SUBMISSION` | `587` | SMTP submission port (STARTTLS) | | `MAX_RECIPIENTS` | `5` | Maximum recipients per email | | `CERT_PATH` | `/certs` | Path to certificate files | | `ACME_JSON_PATH` | `/certs/acme.json` | Path to Traefik acme.json file | See `.env.self-host.example` in the repository root for full configuration options. ## Development ### Prerequisites - Node.js 20+ - PostgreSQL database (running via `yarn services:up`) - Redis (running via `yarn services:up`) ### Running Locally ```bash # Install dependencies yarn install # Start development server (with hot reload) yarn workspace smtp dev ``` The SMTP server will start on ports 465 and 587. For local testing without TLS certificates, it will run in plaintext mode on port 587. ### Testing with Telnet ```bash # Connect to SMTP server telnet localhost 587 # Example SMTP session: EHLO localhost AUTH LOGIN cGx1bms= # base64("plunk") # your project secret (plain text) MAIL FROM: RCPT TO: DATA Subject: Test Email From: sender@yourdomain.com To: recipient@example.com This is a test email. . QUIT ``` ### Testing with Mail Clients Configure your email client with: - **SMTP Server**: `localhost` (or your domain in production) - **Port**: 587 (STARTTLS) or 465 (SSL/TLS) - **Username**: `plunk` - **Password**: Your Plunk API key (project secret) - **From Address**: Must use a verified domain in your Plunk project ## Production Deployment ### TLS Certificate Support The SMTP server supports TLS certificates through two methods: 1. **Traefik acme.json** (recommended for Traefik/Dokploy users) - Mount your Traefik acme.json file to `/certs/acme.json` - Set `SMTP_DOMAIN` environment variable to select the correct certificate - The server will automatically use the certificate for your domain 2. **PEM Files** (standard certificate files) - Mount `privkey.pem` and `fullchain.pem` to `/certs/` - These are standard Let's Encrypt/Certbot filenames - `SMTP_DOMAIN` is optional when using PEM files If no certificates are mounted, the server will run without TLS (not recommended for production). ### Docker Deployment The SMTP server is included in the main Plunk Docker image: **Option 1: With Traefik acme.json** ```bash docker run -d \ -p 465:465 \ -p 587:587 \ -e SERVICE=all \ -e API_URI=https://api.yourdomain.com \ -e SMTP_DOMAIN=smtp.yourdomain.com \ -e DATABASE_URL=postgresql://... \ -e REDIS_URL=redis://... \ -v /path/to/acme.json:/certs/acme.json:ro \ plunk:latest ``` **Option 2: With PEM files** ```bash docker run -d \ -p 465:465 \ -p 587:587 \ -e SERVICE=all \ -e API_URI=https://api.yourdomain.com \ -e DATABASE_URL=postgresql://... \ -e REDIS_URL=redis://... \ -v /etc/letsencrypt/live/smtp.yourdomain.com/privkey.pem:/certs/privkey.pem:ro \ -v /etc/letsencrypt/live/smtp.yourdomain.com/fullchain.pem:/certs/fullchain.pem:ro \ plunk:latest ``` **Option 3: Without TLS (Development Only)** ```bash docker run -d \ -p 587:587 \ -e SERVICE=all \ -e API_URI=http://api.yourdomain.com \ -e DATABASE_URL=postgresql://... \ -e REDIS_URL=redis://... \ plunk:latest ``` **⚠️ Running without TLS is not recommended for production!** ### DNS Configuration Add an A record pointing to your server: ``` smtp.example.com. A 1.2.3.4 ``` Optionally, add MX records if receiving email: ``` example.com. MX 10 smtp.example.com. ``` ## Security ### Authentication - Username must be `plunk` (case-insensitive) - Password is the project secret (API key) - Invalid credentials are rejected with proper SMTP error codes ### Domain Validation - Sender domain must be added to the project - Sender domain must be verified (DNS records validated) - Unverified domains are rejected with clear error messages ### Rate Limiting - Maximum 5 recipients per email by default (configurable) - 10MB maximum message size - Proper error handling for oversized messages ## Monitoring The SMTP server logs all operations using `signale`: ``` ✅ SMTP server listening on port 465 (secure=true) ✅ SMTP server listening on port 587 (secure=false) ✅ Email relayed: sender@domain.com → recipient@example.com ❌ Sender domain is not verified or not associated with your account ``` Use PM2 or similar process managers to monitor the service in production. ## Troubleshooting ### TLS Certificate Issues If TLS is not working: 1. Verify certificates are mounted correctly: ```bash docker exec ls -la /certs/ ``` 2. Check certificate permissions (should be readable) 3. Review logs for certificate loading errors: ```bash docker logs 2>&1 | grep -i "cert" ``` 4. If using Traefik acme.json, ensure `SMTP_DOMAIN` is set correctly 5. If using PEM files, ensure both `privkey.pem` and `fullchain.pem` are present ### Connection Refused If clients cannot connect: 1. Verify ports 465 and 587 are exposed and not blocked by firewall 2. Check if the service is running: `pm2 list` 3. Review logs: `pm2 logs smtp` ### Authentication Failures If authentication fails: 1. Verify username is exactly `plunk` 2. Verify password is the project secret, not public key 3. Check database connectivity ### Domain Verification Errors If emails are rejected with domain errors: 1. Verify domain is added to your project 2. Check domain verification status in Plunk dashboard 3. Ensure DNS records are properly configured ## Performance Considerations The SMTP server is designed for high-scale email sending: - **Async Processing**: All database and API calls are non-blocking - **Connection Pooling**: Prisma handles database connection pooling - **Minimal Memory**: Streams email data without buffering entire messages - **Fast Authentication**: Single database query per connection For high-volume sending: - Deploy multiple SMTP server instances behind a load balancer - Use connection pooling in your SMTP clients - Monitor API rate limits and adjust accordingly ## API Integration The SMTP server forwards emails to the API endpoint: ``` POST /v1/send Authorization: Bearer {project_secret} { "from": "sender@domain.com", "name": "Sender Name", "to": ["recipient@example.com"], "subject": "Email Subject", "body": "..." } ``` The API response is translated to SMTP status codes: - `200 OK` → `250 Message accepted` - `4xx/5xx` → `554 Transaction failed` ## Contributing When modifying the SMTP server: 1. Follow the existing code patterns 2. Add proper error handling 3. Update TypeScript types 4. Test with real SMTP clients 5. Update this README if adding features ## License Part of the Plunk project - see repository root for license information.