How to Send Email from FreeBSD Servers with DMA
Every FreeBSD server needs to send email. Cron job failures, security alerts, ZFS scrub reports, and periodic maintenance notifications all generate mail. The question is how to deliver it reliably without running a full mail server.
DMA (DragonFly Mail Agent) is the answer. It ships in the FreeBSD base system since FreeBSD 14.0, replacing Sendmail as the default MTA. DMA is a lightweight, send-only mail transfer agent that relays outgoing email through an upstream SMTP server. It does not receive mail, does not run a listening daemon, and has a configuration file that is 5 lines long.
This guide covers configuring DMA on FreeBSD to send system mail through Gmail, AWS SES, Mailgun, and generic SMTP relays. It also covers cron notification setup, troubleshooting delivery issues, and common pitfalls.
Why DMA Over Sendmail or Postfix
Sendmail is powerful but complex. Its configuration syntax is notoriously difficult, it runs as a listening daemon by default (attack surface), and it is overkill for a server that only needs to send notifications. FreeBSD historically included Sendmail in the base system, but maintaining it for outbound-only use is unnecessary overhead.
Postfix is an excellent MTA for receiving and relaying mail, but it is a package dependency, runs multiple daemons, and requires significant configuration for what is fundamentally a simple task: "send this email through an SMTP relay."
DMA does one thing well: it accepts mail from local processes (cron, periodic, applications) and delivers it through an upstream SMTP server using TLS authentication. No listening ports. No daemon process. No complex configuration. It is invoked on demand by the mail submission path and exits when delivery is complete.
Prerequisites
- FreeBSD 14.0 or later (DMA is in the base system)
- An SMTP relay account (Gmail, AWS SES, Mailgun, or your organization's mail server)
- Root access
Step 1: Verify DMA Is the Active Mailer
On FreeBSD 14.0+, DMA is the default. Verify:
shls -la /usr/sbin/sendmail
This should be a symlink to /usr/libexec/dma or DMA should be configured in /etc/mail/mailer.conf:
shcat /etc/mail/mailer.conf
Expected output:
shellsendmail /usr/libexec/dma mailq /usr/libexec/dma newaliases /usr/libexec/dma
If your system still uses Sendmail, switch to DMA:
shcat > /etc/mail/mailer.conf << 'EOF' sendmail /usr/libexec/dma mailq /usr/libexec/dma newaliases /usr/libexec/dma EOF # Disable Sendmail sysrc sendmail_enable="NO" sysrc sendmail_submit_enable="NO" sysrc sendmail_outbound_enable="NO" sysrc sendmail_msp_queue_enable="NO" service sendmail stop 2>/dev/null
Step 2: Configure DMA
DMA uses two configuration files:
/etc/dma/dma.conf-- Main configuration/etc/dma/auth.conf-- SMTP authentication credentials
Generic SMTP Relay Configuration
shcat > /etc/dma/dma.conf << 'EOF' # SMTP relay server SMARTHOST smtp.example.com PORT 587 # Authentication AUTHPATH /etc/dma/auth.conf # TLS encryption SECURETRANSFER STARTTLS # Masquerade sender address MASQUERADE admin@example.com # Queue management DEFER FULLBOUNCE EOF
Set SMTP credentials:
shcat > /etc/dma/auth.conf << 'EOF' admin@example.com|smtp.example.com:YourSMTPPassword EOF chmod 640 /etc/dma/auth.conf chown root:mail /etc/dma/auth.conf
The format is user|server:password. The user must match the SMTP login username.
Test the Configuration
shecho "Test email from FreeBSD server $(hostname)" | mail -s "DMA Test" recipient@example.com
Check the DMA queue for stuck messages:
shls -la /var/spool/dma/
An empty spool directory means all messages were delivered. If files remain, check /var/log/maillog for errors:
shtail -20 /var/log/maillog
Step 3: Gmail SMTP Configuration
Gmail requires an App Password if two-factor authentication is enabled (and it should be).
Generate a Gmail App Password
- Go to https://myaccount.google.com/apppasswords
- Select "Mail" and "Other (FreeBSD Server)"
- Copy the generated 16-character password
Configure DMA for Gmail
shcat > /etc/dma/dma.conf << 'EOF' SMARTHOST smtp.gmail.com PORT 587 AUTHPATH /etc/dma/auth.conf SECURETRANSFER STARTTLS MASQUERADE yourname@gmail.com EOF cat > /etc/dma/auth.conf << 'EOF' yourname@gmail.com|smtp.gmail.com:abcd efgh ijkl mnop EOF chmod 640 /etc/dma/auth.conf chown root:mail /etc/dma/auth.conf
Replace abcd efgh ijkl mnop with the App Password from Google (spaces included).
Test
shecho "Gmail test from $(hostname) at $(date)" | mail -s "FreeBSD DMA Test" recipient@example.com
Gmail Sending Limits
Gmail limits outgoing mail to 500 messages per day for regular accounts and 2,000 for Google Workspace. For a server sending cron notifications and alerts, this is more than enough. For bulk sending, use AWS SES or Mailgun.
Step 4: AWS SES Configuration
Amazon Simple Email Service is the most cost-effective option for high-volume transactional email.
Prerequisites
- An AWS account with SES configured
- A verified domain or email address in SES
- SMTP credentials generated in the SES console (these are different from IAM credentials)
Configure DMA for AWS SES
shcat > /etc/dma/dma.conf << 'EOF' SMARTHOST email-smtp.us-east-1.amazonaws.com PORT 587 AUTHPATH /etc/dma/auth.conf SECURETRANSFER STARTTLS MASQUERADE noreply@yourdomain.com EOF
Replace us-east-1 with your SES region.
shcat > /etc/dma/auth.conf << 'EOF' AKIAIOSFODNN7EXAMPLE|email-smtp.us-east-1.amazonaws.com:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY EOF chmod 640 /etc/dma/auth.conf chown root:mail /etc/dma/auth.conf
The username is your SES SMTP username (starts with AKIA...), and the password is the SES SMTP password.
Verify Sender Domain
AWS SES requires verified sender addresses. The MASQUERADE address must match a verified identity in SES. In sandbox mode, recipient addresses must also be verified.
Move Out of SES Sandbox
New SES accounts start in sandbox mode (can only send to verified addresses). Request production access through the AWS console to send to any recipient.
Step 5: Mailgun Configuration
Mailgun provides a generous free tier and excellent deliverability.
shcat > /etc/dma/dma.conf << 'EOF' SMARTHOST smtp.mailgun.org PORT 587 AUTHPATH /etc/dma/auth.conf SECURETRANSFER STARTTLS MASQUERADE noreply@mg.yourdomain.com EOF cat > /etc/dma/auth.conf << 'EOF' postmaster@mg.yourdomain.com|smtp.mailgun.org:your-mailgun-smtp-password EOF chmod 640 /etc/dma/auth.conf chown root:mail /etc/dma/auth.conf
The username and password come from Mailgun's domain settings under SMTP credentials.
Step 6: Cron Job Notifications
FreeBSD's cron sends the output of failed jobs (or all jobs, depending on configuration) via email.
Configure Cron Email Recipient
Set the MAILTO variable in the crontab:
shcrontab -e
Add at the top:
shellMAILTO=admin@example.com MAILFROM=cron@yourdomain.com
All cron job output will now be emailed to admin@example.com.
Per-Job Email
Override the recipient for specific jobs:
shell# Send disk alerts to the ops team MAILTO=ops@example.com 0 * * * * /usr/local/bin/disk-check.sh # Send backup reports to the admin MAILTO=admin@example.com 0 3 * * * /usr/local/bin/backup.sh
Suppress Successful Job Output
To only receive emails when jobs fail, redirect stdout and keep stderr:
shell0 3 * * * /usr/local/bin/backup.sh > /dev/null
This suppresses normal output. Errors (stderr) still generate an email.
Step 7: FreeBSD periodic(8) Notifications
FreeBSD's periodic system (daily, weekly, monthly maintenance) generates reports that are emailed to root by default.
Configure periodic Email
Edit /etc/periodic.conf:
shcat >> /etc/periodic.conf << 'EOF' # Send periodic reports to admin daily_output="admin@example.com" weekly_output="admin@example.com" monthly_output="admin@example.com" # Security reports to a separate address daily_status_security_output="security@example.com" EOF
Mail Aliases
Configure /etc/aliases to redirect root's mail:
shcat > /etc/aliases << 'EOF' # Root email forwarding root: admin@example.com manager: admin@example.com postmaster: admin@example.com EOF # Rebuild the alias database (DMA uses this file directly) newaliases
Step 8: Sending Email from Scripts
Using mail(1)
sh# Simple message echo "Backup completed successfully" | mail -s "Backup Report: $(hostname)" admin@example.com # Multi-line message mail -s "ZFS Scrub Report" admin@example.com << 'REPORT' ZFS scrub completed on $(hostname) Date: $(date) Pool status: $(zpool status) ARC statistics: $(sysctl kstat.zfs.misc.arcstats.hits kstat.zfs.misc.arcstats.misses) REPORT
Using mailx with Attachments
DMA works with mailx for sending attachments:
sh# Send a log file as an attachment pkg install mailx echo "See attached log" | mailx -s "Server Log" -a /var/log/messages admin@example.com
From Shell Scripts
sh#!/bin/sh # alert.sh -- send an alert email RECIPIENT="admin@example.com" SUBJECT="ALERT: $(hostname) - $1" BODY="$2" echo "$BODY" | mail -s "$SUBJECT" "$RECIPIENT"
Usage:
sh/usr/local/bin/alert.sh "Disk Space Low" "Filesystem /var is 95% full on $(hostname)"
Integrating with ZFS Events
Create a ZFS Event Daemon (ZED) email script:
sh# Ensure /etc/zfs/zed.d/zed.rc has: ZED_EMAIL_ADDR="admin@example.com" ZED_EMAIL_PROG="mail" ZED_NOTIFY_VERBOSE=1
ZED will email you when ZFS detects errors, scrubs complete, or resilvers finish.
Step 9: Sender Authentication (SPF, DKIM)
Even with a relay service, email deliverability depends on proper sender authentication.
SPF Record
Add an SPF record to your domain's DNS:
shellyourdomain.com. IN TXT "v=spf1 include:_spf.google.com ~all"
For AWS SES:
shellyourdomain.com. IN TXT "v=spf1 include:amazonses.com ~all"
For Mailgun:
shellyourdomain.com. IN TXT "v=spf1 include:mailgun.org ~all"
DKIM
DKIM signing is handled by the relay service (Gmail, SES, Mailgun). Configure it in the relay provider's dashboard, not on the FreeBSD server. Add the DKIM DNS record they provide.
DMARC
Add a DMARC record for monitoring:
shell_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"
Start with p=none (monitor only), review reports, then tighten to p=quarantine or p=reject.
Troubleshooting
Email Stuck in the Queue
sh# Check the DMA spool ls -la /var/spool/dma/ # View queued message details cat /var/spool/dma/M* # Check the mail log tail -50 /var/log/maillog
Common causes: wrong SMTP credentials, firewall blocking port 587, DNS resolution failure, or TLS certificate verification failure.
Authentication Failures
sh# Verify credentials format in auth.conf cat /etc/dma/auth.conf # Format must be: user|server:password # Test SMTP connectivity manually openssl s_client -connect smtp.gmail.com:587 -starttls smtp
Type EHLO localhost after the connection establishes. A 250 response confirms SMTP connectivity.
TLS Certificate Errors
If DMA reports TLS errors:
sh# Verify the CA certificate bundle exists ls -la /etc/ssl/cert.pem # Test TLS to the relay openssl s_client -connect smtp.gmail.com:587 -starttls smtp -CAfile /etc/ssl/cert.pem
If the certificate bundle is missing or outdated:
shpkg install ca_root_nss
Emails Arriving in Spam
Check sender authentication:
sh# Verify SPF dig TXT yourdomain.com | grep spf # Verify DKIM (check with your relay provider) # Verify DMARC dig TXT _dmarc.yourdomain.com
Also verify that the MASQUERADE address in dma.conf matches a verified sender in your relay service.
Port 587 Blocked
Some hosting providers block outbound SMTP ports. Test connectivity:
shnc -zv smtp.gmail.com 587
If blocked, try port 465 (SMTPS) instead:
sh# dma.conf SMARTHOST smtp.gmail.com PORT 465 AUTHPATH /etc/dma/auth.conf SECURETRANSFER # Remove STARTTLS -- port 465 uses implicit TLS
No /var/log/maillog
Ensure syslog is configured to log mail:
shgrep mail /etc/syslog.conf
Expected line:
shellmail.* /var/log/maillog
If missing, add it and restart syslog:
shecho 'mail.* /var/log/maillog' >> /etc/syslog.conf service syslogd restart
DMA Configuration Reference
| Directive | Purpose | Example |
|-----------|---------|---------|
| SMARTHOST | SMTP relay server | smtp.gmail.com |
| PORT | SMTP port | 587 |
| AUTHPATH | Path to credentials file | /etc/dma/auth.conf |
| SECURETRANSFER | Enable TLS | (no value) |
| STARTTLS | Use STARTTLS (port 587) | (no value) |
| MASQUERADE | Sender address | noreply@example.com |
| DEFER | Queue messages on failure | (no value) |
| FULLBOUNCE | Include original message in bounces | (no value) |
| MAILNAME | HELO hostname | server.example.com |
FAQ
Is DMA available on FreeBSD 13?
DMA is available as a package (pkg install dma) on FreeBSD 13, but it is not the default MTA. On FreeBSD 14.0+, DMA is in the base system and replaces Sendmail as the default. On FreeBSD 13, install it and update /etc/mail/mailer.conf manually.
Can DMA receive incoming email?
No. DMA is a send-only MTA. It does not listen on any ports and cannot accept incoming mail. For receiving email, use Postfix, OpenSMTPD, or a hosted email service.
How many emails can DMA queue?
DMA stores queued messages as individual files in /var/spool/dma/. There is no hard limit, but each message is a separate file. On a healthy system with a working relay, the queue should be empty. A growing queue indicates delivery problems.
Should I use port 587 (STARTTLS) or port 465 (SMTPS)?
Port 587 with STARTTLS is the current standard (RFC 6409). Port 465 with implicit TLS was deprecated but re-standardized (RFC 8314). Both work. Use 587 as the default; fall back to 465 if 587 is blocked.
Can I use DMA with Microsoft 365 / Outlook?
Yes. Use smtp.office365.com as the SMARTHOST, port 587, and authenticate with a valid Microsoft 365 account. Ensure the account has an Exchange Online license. Modern authentication (OAuth2) is not supported by DMA; use an app password or a service account with basic auth enabled.
How do I send email from a FreeBSD jail?
DMA works inside jails. Install DMA (or symlink it from the host's base if using thin jails), configure /etc/dma/dma.conf inside the jail, and ensure the jail has outbound network access on port 587.
What happens if the SMTP relay is down?
With the DEFER directive, DMA queues the message in /var/spool/dma/ and retries on the next invocation. DMA does not run a background queue runner; retries happen when the next email is sent or when you manually flush the queue with dma -q. For reliable delivery, add a cron job to flush the queue periodically:
sh# Flush DMA queue every 5 minutes */5 * * * * root /usr/libexec/dma -q