Getting clean, reliable verification and welcome emails is the difference between a hobby app and something people trust. This guide shows how to send outbound mail through Gmail SMTP on Ubuntu using msmtp, wire it into a typical PHP app, and set up SPF and DMARC so your emails land where they should.
We use this approach in our own services for low-volume transactional mail. The steps below are generic and work for any self-hosted application.
Why This Approach?
This method offers a simple, maintainable, and low-cost way to handle transactional emails for side projects and prototypes, ensuring good deliverability from the start.
Benefits
- Simple & Maintainable: One lightweight tool (
msmtp) acts likesendmailand relays via Gmail—no heavyweight Mail Transfer Agent (MTA) to run. - Good Deliverability: Gmail's infrastructure, plus SPF and DMARC (in monitor mode), reduces the risk of landing in spam for small-to-medium volume.
- Low Cost: No extra services are required, making it perfect for side projects.
- Portable: Your app only "knows" it's calling
sendmail. Swapping to another SMTP provider (like Google Workspace, Postmark, or AWS SES) later is a configuration change, not a code rewrite. - Debuggable:
msmtplogs each delivery attempt to a local file, which makes troubleshooting straightforward.
Risks & Trade-offs
- Rate Limits: Personal Gmail accounts aren't for bulk sending. Keep volume modest; for scale, move to Google Workspace or a dedicated transactional provider.
- DMARC Alignment Nuance: With personal Gmail, DKIM is signed by
gmail.com, not your domain, so strict DMARC alignment can fail. We'll mitigate this by using relaxed alignment. - Policy & Reputation: Personal Gmail isn't intended for newsletters or bulk marketing. Use it for transactional mail only (e.g., OTPs, welcome emails, password resets).
- Secret Handling: App Passwords must be protected. Store them in a root-owned file readable only by the application user.
- No Inbound Mailboxes: This guide covers outbound email only. If you need to receive mail, you'll need to add a mailbox provider like Google Workspace.
The Technology Stack
Here's a quick look at the components involved:
- SMTP over TLS: Securely connecting to
smtp.gmail.comon port 587. - msmtp: A tiny SMTP client that emulates
sendmail, allowing your app to use standard mail functions without running a full MTA. - PHP-FPM Transport: We'll configure PHP to use
msmtpas itssendmail_path. - SPF (Sender Policy Framework): A DNS record authorizing Google to send emails on behalf of your domain.
- DMARC (Domain-based Message Authentication, Reporting & Conformance): A DNS record that sets policy and reporting for your domain's email.
High-Level Flow:
Your App → (mail()/sendmail) → msmtp → smtp.gmail.com (TLS) → Recipient
DNS provides SPF + DMARC records for authentication.
Prerequisites
- An Ubuntu 22.04+ server with root or
sudoaccess. - PHP-FPM (if following the PHP example).
- A Gmail account with 2-Step Verification enabled.
- A Gmail App Password generated for this purpose.
- Access to your domain's DNS settings.
Step 1: Configure DNS (SPF and DMARC)
Add the following TXT records at your DNS provider. This tells receiving mail servers that Google is an authorized sender for your domain.
SPF Record
Create a TXT record for your root domain (@):
v=spf1 include:_spf.google.com ~all
DMARC Record
Create a TXT record for _dmarc.yourdomain.com:
v=DMARC1; p=none; sp=none; adkim=r; aspf=r; rua=mailto:[email protected]
p=none: Start in monitoring mode. Tighten this later once you're confident in your setup.adkim=r,aspf=r: Use relaxed alignment to work correctly with personal Gmail's DKIM signing.rua: Specifies where aggregate reports should be sent. Be sure to create this mailbox.
Step 2: Install and Configure msmtp
First, install the necessary packages on your server.
sudo apt update
sudo apt -y install msmtp msmtp-mta
Next, create the main configuration file at /etc/msmtprc. This file will not contain any secrets.
# /etc/msmtprc
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
account default
host smtp.gmail.com
port 587
from [email protected]
user [email protected]
passwordeval "cat /etc/msmtp_pass.txt"
Now, store your 16-character Gmail App Password in a separate, secure file and set strict permissions so only the web server user (www-data) can read it.
# Store the password
echo 'YOUR_16_CHAR_APP_PASSWORD' | sudo tee /etc/msmtp_pass.txt >/dev/null
# Set permissions for the password file
sudo chown www-data:www-data /etc/msmtp_pass.txt
sudo chmod 600 /etc/msmtp_pass.txt
# Create and set permissions for the log file
sudo touch /var/log/msmtp.log
sudo chown www-data:www-data /var/log/msmtp.log
sudo chmod 640 /var/log/msmtp.log
Step 3: Point PHP to msmtp
Tell PHP to use msmtp for sending mail by creating a new configuration file. Remember to replace 8.1 with your installed PHP version.
sudo tee /etc/php/8.1/fpm/conf.d/90-msmtp.ini >/dev/null <<'INI'
sendmail_path = /usr/bin/msmtp -t -i
session.cookie_secure = 1
session.cookie_httponly = 1
INI
sudo systemctl restart php8.1-fpm
Not using PHP? Just configure your language's SMTP client directly with the Gmail host, port, TLS settings, your Gmail address, and the App Password.
Step 4: Send a Test Email from the CLI
Let's send a test email from the command line, running the command as the www-data user to ensure permissions are correct.
printf "Subject: msmtp test\nTo: [email protected]\nFrom: Your App \n\nHello from msmtp.\n" \
| sudo -u www-data /usr/bin/msmtp -a default -t
# Check the log for success or errors
tail -n 20 /var/log/msmtp.log
Look for a log entry showing exitcode=EX_OK. If you see an authentication error like 535 5.7.8, double-check that 2FA is enabled and your App Password is correct.
Step 5: Wire Into Your App (PHP Example)
Here is a minimal PHP helper function to send multipart (HTML + text) emails. Using tables for layout ensures compatibility with most email clients.
Mail Helper Function
function send_mail($to, $subject, $html, $text) {
$fromName = 'Your App';
$from = '[email protected]';
$boundary = 'b_' . bin2hex(random_bytes(8));
$headers = "From: {$fromName} \r\n" .
"MIME-Version: 1.0\r\n" .
"Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
$body = "--{$boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n{$text}\r\n";
$body .= "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n{$html}\r\n";
$body .= "--{$boundary}--\r\n";
// base64 encode the subject for UTF-8 compatibility
$encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
return @mail($to, $encodedSubject, $body, $headers);
}
Example: Sending a Verification Code
$code = random_int(100000, 999999);
// HTML part of the email
$html = '
Your verification code
Use this code to finish setting up your account:
'
. htmlspecialchars($code) .
'
This code expires in 30 minutes.
';
// Plain text alternative
$text = "Your verification code: {$code}\nThis code expires in 30 minutes.\n";
// Send the email
send_mail('[email protected]', 'Verify Your Email Address', $html, $text);
Good Practice: Store only a hash of the verification code (e.g., using password_hash), enforce a time-to-live (TTL), and add a rate limit for resend requests.
Production Tips & Next Steps
Best Practices
- Tempo & Volume: Keep sends transactional; pace resends and avoid large bursts.
- Content Hygiene: Use minimal HTML, write clear subject lines, and always include a text alternative.
- Security: Rotate App Passwords if they are ever exposed. Never log sensitive data like OTP codes.
- Observability: Monitor
/var/log/msmtp.logand add app-level counters or alerts for failures.
When to Upgrade
Consider moving to a dedicated service like Google Workspace, Postmark, or AWS SES when:
- You need strict DMARC alignment (with DKIM signed by your domain).
- You need to handle inbound mail or use aliases.
- You expect higher throughput or require advanced features like webhooks, suppression lists, and analytics.
Troubleshooting Quick Hits
/usr/bin/msmtp: command not found→ Runsudo apt install msmtp msmtp-mta.- Gmail
535 5.7.8error → Ensure 2FA is enabled and you are using a valid App Password, not your regular account password. - Emails land in spam → Allow 24-48 hours for DNS to propagate, keep HTML lean, and warm up your sending volume gradually.
- Still failing DMARC alignment → This is expected with personal Gmail. Use the relaxed alignment settings (
adkim=r; aspf=r) or move to a service that allows you to sign with your own domain's DKIM key.
For small to medium self-hosted apps, Gmail SMTP with msmtp is a pragmatic, rock-solid path to reliable transactional emails. It teaches you the right DNS controls, keeps your code simple, and provides a clear upgrade path for when you're ready to scale.