Files
nixos/hosts/amzebs-01/EMAIL_SETUP.md

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.

  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:

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

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

  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:

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

  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

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