12 KiB
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)
# 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)
# 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
cat /tmp/dkim-gen/amz.at.amzebs-01.key
Step 2: Edit the secrets file
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:
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
rm -rf /tmp/dkim-gen
3. Verify Secret is Encrypted
Check that the secret is properly encrypted:
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:
# 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:
# 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:
sudo nixos-rebuild switch
2. Verify Deployment
Check that the services are running:
# 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.
- Log into your hosting provider's control panel
- Find the "Reverse DNS" or "PTR Record" section
- Set the PTR record for IP
23.88.38.1to point toamzebs-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:
# 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:
# 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:
# 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 top=rejectafter testing)rua=mailto:...: Aggregate reports sent to this addressruf=mailto:...: Forensic reports sent to this addressfo=1: Generate forensic reports for any failure
Laravel Configuration
Update your Laravel application's .env file:
Option A: Using sendmail (Recommended)
MAIL_MAILER=sendmail
MAIL_FROM_ADDRESS=noreply@amz.at
MAIL_FROM_NAME="${APP_NAME}"
Option B: Using SMTP
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
# Send a test email
echo "Test email body" | mail -s "Test Subject" test@example.com -aFrom:test@amz.at
Check Postfix Queue
# View mail queue
mailq
# View logs
journalctl -u postfix -f
Check Rspamd Logs
# View rspamd logs
journalctl -u rspamd -f
Test DKIM Signature and Deliverability
Send an email to test your complete email configuration:
Email Testing Services
-
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
-
MXToolbox Email Health (https://mxtoolbox.com/emailhealth/)
- Comprehensive deliverability check
- Checks DNS records, blacklists, configuration
-
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:
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
# On the server
echo "HELO test" | nc localhost 25
# Should see: 250 amzebs-01.amz.at
Verification Commands
# 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
- Localhost-only: Postfix is configured to listen ONLY on 127.0.0.1
- No authentication: Not needed since only local processes can connect
- No firewall changes: No external ports opened for email
- DKIM signing: All outgoing emails are automatically signed with DKIM
- Host-specific key: Using selector "amzebs-01" allows multiple hosts to send for amz.at
Troubleshooting
Email not being sent
- Check Postfix status:
systemctl status postfix - Check queue:
mailq - Check logs:
journalctl -u postfix -n 100
DKIM not signing
- Check Rspamd status:
systemctl status rspamd - Check if key exists:
ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key - Check Rspamd logs:
journalctl -u rspamd -n 100
Permission errors
# Ensure proper ownership
chown -R rspamd:rspamd /var/lib/rspamd/dkim/
chmod 600 /var/lib/rspamd/dkim/*.key
Rotate DKIM key
# 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:
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.