# 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.
