472 lines
12 KiB
Markdown
472 lines
12 KiB
Markdown
# Email Setup for amzebs-01 (amz.at)
|
|
|
|
This host is configured to send emails via Laravel with DKIM signing.
|
|
|
|
## Configuration Overview
|
|
|
|
- **Postfix**: Localhost-only SMTP server (no external access)
|
|
- **Rspamd**: DKIM signing with host-specific key
|
|
- **Domain**: amz.at
|
|
- **DKIM Selector**: amzebs-01
|
|
- **Secret Management**: DKIM private key stored in sops
|
|
|
|
## Initial Setup (Before First Deployment)
|
|
|
|
### 1. Generate DKIM Key Pair
|
|
|
|
You need to generate a DKIM key pair locally first. You'll need `rspamd` package installed.
|
|
|
|
#### Option A: Using rspamd (if installed locally)
|
|
|
|
```bash
|
|
# Create a temporary directory
|
|
mkdir -p /tmp/dkim-gen
|
|
|
|
# Generate the key pair
|
|
rspamadm dkim_keygen -s amzebs-01 -d amz.at -k /tmp/dkim-gen/amz.at.amzebs-01.key
|
|
```
|
|
|
|
This will output:
|
|
- **Private key** saved to `/tmp/dkim-gen/amz.at.amzebs-01.key`
|
|
- **Public key** printed to stdout (starts with `v=DKIM1; k=rsa; p=...`)
|
|
|
|
#### Option B: Using OpenSSL (alternative)
|
|
|
|
```bash
|
|
# Create temporary directory
|
|
mkdir -p /tmp/dkim-gen
|
|
|
|
# Generate private key (2048-bit RSA)
|
|
openssl genrsa -out /tmp/dkim-gen/amz.at.amzebs-01.key 2048
|
|
|
|
# Extract public key in the correct format for DNS
|
|
openssl rsa -in /tmp/dkim-gen/amz.at.amzebs-01.key -pubout -outform PEM | \
|
|
grep -v '^-----' | tr -d '\n' > /tmp/dkim-gen/public.txt
|
|
|
|
# Display the DNS record value
|
|
echo "v=DKIM1; k=rsa; p=$(cat /tmp/dkim-gen/public.txt)"
|
|
```
|
|
|
|
**Save the public key output!** You'll need it for DNS configuration later.
|
|
|
|
### 2. Add DKIM Private Key to Sops Secrets
|
|
|
|
Now you need to encrypt and add the private key to your secrets file.
|
|
|
|
#### Step 1: View the private key
|
|
|
|
```bash
|
|
cat /tmp/dkim-gen/amz.at.amzebs-01.key
|
|
```
|
|
|
|
#### Step 2: Edit the secrets file
|
|
|
|
```bash
|
|
cd /home/dominik/projects/cloonar/cloonar-nixos/hosts/amzebs-01
|
|
sops secrets.yaml
|
|
```
|
|
|
|
#### Step 3: Add the key to secrets.yaml
|
|
|
|
In the sops editor, add a new key called `rspamd-dkim-key` with the **entire private key content** including the BEGIN/END markers:
|
|
|
|
```yaml
|
|
rspamd-dkim-key: |
|
|
-----BEGIN PRIVATE KEY-----
|
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
|
(paste the entire key content here)
|
|
...
|
|
-----END PRIVATE KEY-----
|
|
```
|
|
|
|
**Important:**
|
|
- Make sure to use the pipe `|` character for multiline content
|
|
- Keep the proper indentation (2 spaces before each line of the key)
|
|
- Include the full BEGIN/END markers
|
|
|
|
#### Step 4: Save and exit
|
|
|
|
Save the file in sops (it will be encrypted automatically).
|
|
|
|
#### Step 5: Clean up temporary files
|
|
|
|
```bash
|
|
rm -rf /tmp/dkim-gen
|
|
```
|
|
|
|
### 3. Verify Secret is Encrypted
|
|
|
|
Check that the secret is properly encrypted:
|
|
|
|
```bash
|
|
cat hosts/amzebs-01/secrets.yaml
|
|
```
|
|
|
|
You should see encrypted content, not the plain private key.
|
|
|
|
### 4. Extract Public Key for DNS (if needed later)
|
|
|
|
If you didn't save the public key earlier, you can extract it after deployment:
|
|
|
|
```bash
|
|
# On the server after deployment
|
|
sudo cat /var/lib/rspamd/dkim/amz.at.amzebs-01.key | \
|
|
openssl rsa -pubout -outform PEM 2>/dev/null | \
|
|
grep -v '^-----' | tr -d '\n'
|
|
```
|
|
|
|
Then format it as:
|
|
```
|
|
v=DKIM1; k=rsa; p=<output_from_above>
|
|
```
|
|
|
|
## Deployment
|
|
|
|
### 1. Deploy Configuration
|
|
|
|
After adding the DKIM private key to sops, deploy the configuration:
|
|
|
|
```bash
|
|
# Build and switch on the remote host
|
|
nixos-rebuild switch --flake .#amzebs-01 --target-host amzebs-01 --use-remote-sudo
|
|
```
|
|
|
|
Or if deploying locally on the server:
|
|
|
|
```bash
|
|
sudo nixos-rebuild switch
|
|
```
|
|
|
|
### 2. Verify Deployment
|
|
|
|
Check that the services are running:
|
|
|
|
```bash
|
|
# Check rspamd-dkim-setup service
|
|
systemctl status rspamd-dkim-setup
|
|
|
|
# Check that rspamd is running
|
|
systemctl status rspamd
|
|
|
|
# Check that postfix is running
|
|
systemctl status postfix
|
|
|
|
# Verify DKIM key was deployed
|
|
ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key
|
|
```
|
|
|
|
## DNS Configuration
|
|
|
|
Add the following DNS records to ensure proper email delivery and avoid spam classification.
|
|
|
|
### Critical: PTR Record (Reverse DNS)
|
|
|
|
**This is CRITICAL for email deliverability!** Without a proper PTR record, most mail servers will reject or spam your emails.
|
|
|
|
#### What is a PTR Record?
|
|
A PTR (pointer) record is a reverse DNS entry that maps your IP address back to your hostname. Mail servers use this to verify you're a legitimate mail server.
|
|
|
|
#### Required PTR Record
|
|
```
|
|
IP Address: 23.88.38.1
|
|
Points to: amzebs-01.amz.at
|
|
```
|
|
|
|
#### How to Configure PTR Record
|
|
|
|
**Step 1: Contact Your Hosting Provider**
|
|
|
|
PTR records MUST be configured through your hosting provider (e.g., Hetzner, OVH, AWS, etc.). You cannot set PTR records through your domain registrar.
|
|
|
|
1. Log into your hosting provider's control panel
|
|
2. Find the "Reverse DNS" or "PTR Record" section
|
|
3. Set the PTR record for IP `23.88.38.1` to point to `amzebs-01.amz.at`
|
|
|
|
**Common Provider Links:**
|
|
- **Hetzner**: Robot panel → IPs → Edit reverse DNS
|
|
- **OVH**: Network → IP → ... → Modify reverse
|
|
- **AWS EC2**: Select instance → Networking → Request reverse DNS
|
|
|
|
**Step 2: Verify Forward DNS First**
|
|
|
|
Before setting the PTR record, ensure your forward DNS is correct:
|
|
|
|
```bash
|
|
# This should return 23.88.38.1
|
|
dig +short amzebs-01.amz.at A
|
|
host amzebs-01.amz.at
|
|
```
|
|
|
|
**Step 3: Verify PTR Record**
|
|
|
|
After configuring, verify the PTR record is working:
|
|
|
|
```bash
|
|
# Method 1: Using dig
|
|
dig +short -x 23.88.38.1
|
|
|
|
# Method 2: Using host
|
|
host 23.88.38.1
|
|
|
|
# Method 3: Using nslookup
|
|
nslookup 23.88.38.1
|
|
```
|
|
|
|
All commands should return: `amzebs-01.amz.at`
|
|
|
|
**Step 4: Verify FCrDNS (Forward-Confirmed Reverse DNS)**
|
|
|
|
This ensures forward and reverse DNS match properly:
|
|
|
|
```bash
|
|
# Forward lookup
|
|
dig +short amzebs-01.amz.at
|
|
# Should output: 23.88.38.1
|
|
|
|
# Reverse lookup
|
|
dig +short -x 23.88.38.1
|
|
# Should output: amzebs-01.amz.at.
|
|
```
|
|
|
|
If both work correctly, FCrDNS passes! ✓
|
|
|
|
**Why PTR Records Matter:**
|
|
- Gmail, Microsoft, Yahoo require valid PTR records
|
|
- Missing PTR = automatic spam classification or rejection
|
|
- Can add 5-10 points to spam score alone
|
|
- Required for professional email delivery
|
|
|
|
### Domain DNS Records (amz.at)
|
|
|
|
Add these records through your domain registrar's DNS management:
|
|
|
|
#### SPF Record
|
|
```
|
|
Type: TXT
|
|
Name: @
|
|
Value: v=spf1 mx a:amzebs-01.amz.at ~all
|
|
```
|
|
|
|
#### DKIM Record
|
|
```
|
|
Type: TXT
|
|
Name: amzebs-01._domainkey
|
|
Value: [Your public key from step 1 above]
|
|
```
|
|
|
|
The DKIM record will look something like:
|
|
```
|
|
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
|
```
|
|
|
|
#### DMARC Record
|
|
```
|
|
Type: TXT
|
|
Name: _dmarc
|
|
Value: v=DMARC1; p=quarantine; rua=mailto:postmaster@amz.at; ruf=mailto:postmaster@amz.at; fo=1
|
|
```
|
|
|
|
**Explanation:**
|
|
- `p=quarantine`: Failed messages should be quarantined (you can change to `p=reject` after testing)
|
|
- `rua=mailto:...`: Aggregate reports sent to this address
|
|
- `ruf=mailto:...`: Forensic reports sent to this address
|
|
- `fo=1`: Generate forensic reports for any failure
|
|
|
|
## Laravel Configuration
|
|
|
|
Update your Laravel application's `.env` file:
|
|
|
|
#### Option A: Using sendmail (Recommended)
|
|
```env
|
|
MAIL_MAILER=sendmail
|
|
MAIL_FROM_ADDRESS=noreply@amz.at
|
|
MAIL_FROM_NAME="${APP_NAME}"
|
|
```
|
|
|
|
#### Option B: Using SMTP
|
|
```env
|
|
MAIL_MAILER=smtp
|
|
MAIL_HOST=127.0.0.1
|
|
MAIL_PORT=25
|
|
MAIL_USERNAME=null
|
|
MAIL_PASSWORD=null
|
|
MAIL_ENCRYPTION=null
|
|
MAIL_FROM_ADDRESS=noreply@amz.at
|
|
MAIL_FROM_NAME="${APP_NAME}"
|
|
```
|
|
|
|
**Note**: Laravel can use ANY email address with @amz.at domain. All will be DKIM signed automatically.
|
|
|
|
## Testing Email
|
|
|
|
### Test from Command Line
|
|
|
|
```bash
|
|
# Send a test email
|
|
echo "Test email body" | mail -s "Test Subject" test@example.com -aFrom:test@amz.at
|
|
```
|
|
|
|
### Check Postfix Queue
|
|
|
|
```bash
|
|
# View mail queue
|
|
mailq
|
|
|
|
# View logs
|
|
journalctl -u postfix -f
|
|
```
|
|
|
|
### Check Rspamd Logs
|
|
|
|
```bash
|
|
# View rspamd logs
|
|
journalctl -u rspamd -f
|
|
```
|
|
|
|
### Test DKIM Signature and Deliverability
|
|
|
|
Send an email to test your complete email configuration:
|
|
|
|
#### Email Testing Services
|
|
1. **Mail Tester** (https://www.mail-tester.com/)
|
|
- Provides a temporary email address
|
|
- Shows comprehensive spam score (0-10, higher is better)
|
|
- Checks DKIM, SPF, DMARC, PTR, blacklists, content
|
|
- **Target: 9/10 or higher**
|
|
|
|
2. **MXToolbox Email Health** (https://mxtoolbox.com/emailhealth/)
|
|
- Comprehensive deliverability check
|
|
- Checks DNS records, blacklists, configuration
|
|
|
|
3. **Google Admin Toolbox** (https://toolbox.googleapps.com/apps/messageheader/)
|
|
- Paste email headers to see how Gmail scored your email
|
|
- Shows SPF, DKIM, DMARC results
|
|
|
|
#### What to Check
|
|
- ✓ DKIM signature is valid
|
|
- ✓ SPF passes
|
|
- ✓ DMARC passes
|
|
- ✓ PTR record (reverse DNS) matches
|
|
- ✓ Not on any blacklists
|
|
- ✓ Spam score < 2.0 (lower is better)
|
|
|
|
#### Common Issues & Fixes
|
|
|
|
**High Spam Score (> 5.0)**
|
|
- Check: PTR record configured correctly? (Critical!)
|
|
- Check: HELO name matches hostname?
|
|
- Check: All headers present (To:, From:, Subject:)?
|
|
- Check: IP not blacklisted?
|
|
|
|
**Missing "To:" Header**
|
|
Your Laravel app must set a recipient. In your code:
|
|
```php
|
|
Mail::to('recipient@example.com')
|
|
->send(new YourMailable());
|
|
```
|
|
|
|
**HELO/EHLO Mismatch**
|
|
After applying this configuration, HELO should be `amzebs-01.amz.at`, not `localhost`
|
|
|
|
**Check Current HELO Name**
|
|
```bash
|
|
# On the server
|
|
echo "HELO test" | nc localhost 25
|
|
# Should see: 250 amzebs-01.amz.at
|
|
```
|
|
|
|
## Verification Commands
|
|
|
|
```bash
|
|
# Check if Postfix is running
|
|
systemctl status postfix
|
|
|
|
# Check if Rspamd is running
|
|
systemctl status rspamd
|
|
|
|
# Check if Postfix is listening on localhost only
|
|
ss -tlnp | grep master
|
|
|
|
# View DKIM public key again
|
|
systemctl start rspamd-show-dkim
|
|
journalctl -u rspamd-show-dkim
|
|
|
|
# Check if DKIM key exists
|
|
ls -la /var/lib/rspamd/dkim/
|
|
```
|
|
|
|
## Security Notes
|
|
|
|
1. **Localhost-only**: Postfix is configured to listen ONLY on 127.0.0.1
|
|
2. **No authentication**: Not needed since only local processes can connect
|
|
3. **No firewall changes**: No external ports opened for email
|
|
4. **DKIM signing**: All outgoing emails are automatically signed with DKIM
|
|
5. **Host-specific key**: Using selector "amzebs-01" allows multiple hosts to send for amz.at
|
|
|
|
## Troubleshooting
|
|
|
|
### Email not being sent
|
|
|
|
1. Check Postfix status: `systemctl status postfix`
|
|
2. Check queue: `mailq`
|
|
3. Check logs: `journalctl -u postfix -n 100`
|
|
|
|
### DKIM not signing
|
|
|
|
1. Check Rspamd status: `systemctl status rspamd`
|
|
2. Check if key exists: `ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key`
|
|
3. Check Rspamd logs: `journalctl -u rspamd -n 100`
|
|
|
|
### Permission errors
|
|
|
|
```bash
|
|
# Ensure proper ownership
|
|
chown -R rspamd:rspamd /var/lib/rspamd/dkim/
|
|
chmod 600 /var/lib/rspamd/dkim/*.key
|
|
```
|
|
|
|
### Rotate DKIM key
|
|
|
|
```bash
|
|
# 1. Generate new key pair locally (follow "Initial Setup" steps)
|
|
# 2. Update the rspamd-dkim-key in secrets.yaml with new key
|
|
# 3. Deploy the configuration
|
|
nixos-rebuild switch
|
|
|
|
# 4. Restart the setup service to copy new key
|
|
systemctl restart rspamd-dkim-setup
|
|
|
|
# 5. Restart rspamd to use new key
|
|
systemctl restart rspamd
|
|
|
|
# 6. Update DNS with new public key
|
|
# 7. Wait for DNS propagation before removing old DNS record
|
|
```
|
|
|
|
## Related Files
|
|
|
|
- Postfix config: `hosts/amzebs-01/modules/postfix.nix`
|
|
- Rspamd config: `hosts/amzebs-01/modules/rspamd.nix`
|
|
- Main config: `hosts/amzebs-01/configuration.nix`
|
|
- Secrets file: `hosts/amzebs-01/secrets.yaml` (encrypted)
|
|
|
|
## Sops Secret Configuration
|
|
|
|
The DKIM private key is stored as a sops secret with the following configuration:
|
|
|
|
```nix
|
|
sops.secrets.rspamd-dkim-key = {
|
|
owner = "rspamd";
|
|
group = "rspamd";
|
|
mode = "0400";
|
|
};
|
|
```
|
|
|
|
This ensures:
|
|
- Only the rspamd user can read the key
|
|
- The key is decrypted at boot time by sops-nix
|
|
- The key is encrypted in version control
|
|
- The key persists across rebuilds
|
|
|
|
The key is automatically copied from the sops secret path to `/var/lib/rspamd/dkim/amz.at.amzebs-01.key` by the `rspamd-dkim-setup.service` on every boot.
|