Files
nixos/hosts/amzebs-01/EMAIL_SETUP.md
2025-11-21 14:00:47 +01:00

8.1 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 your amz.at domain:

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

Send an email to a Gmail account or use an email testing service like:

They will show you:

  • If DKIM signature is valid
  • If SPF passes
  • If DMARC passes
  • Your spam score

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.