Outbound Email for Self-Hosted Apps: Gmail SMTP + msmtp

6 min read
Intermediate Gmail SMTP Ubuntu PHP

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 like sendmail and 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: msmtp logs 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.com on 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 msmtp as its sendmail_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) → msmtpsmtp.gmail.com (TLS) → Recipient

DNS provides SPF + DMARC records for authentication.

Prerequisites

  • An Ubuntu 22.04+ server with root or sudo access.
  • 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.log and 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 → Run sudo apt install msmtp msmtp-mta.
  • Gmail 535 5.7.8 error → 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.

See Also