290 lines
8.0 KiB
Markdown
290 lines
8.0 KiB
Markdown
# 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-api-key> # your project secret (plain text)
|
|
MAIL FROM:<sender@yourdomain.com>
|
|
RCPT TO:<recipient@example.com>
|
|
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 <container> ls -la /certs/
|
|
```
|
|
2. Check certificate permissions (should be readable)
|
|
3. Review logs for certificate loading errors:
|
|
```bash
|
|
docker logs <container> 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": "<html>...</html>"
|
|
}
|
|
```
|
|
|
|
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.
|