Your Self-Hosted App Can’t Send Emails
You set up Vaultwarden for your team, configured Nextcloud for file sharing, or deployed Twenty CRM for your sales pipeline. Everything works — except email. Password resets never arrive. Invitations vanish. Notifications go nowhere.
We help customers with this at Canadian Web Hosting every week. The error messages are frustratingly vague: Connection refused, Authentication failed, Connection timed out. But the underlying causes follow predictable patterns. This guide walks through the six most common SMTP configuration failures in self-hosted applications — and exactly how to fix each one.
If your emails are bouncing after they leave your server (not failing at the SMTP stage), check our Email Delivery Problems guide for SPF, DKIM, and DMARC configuration instead.
Quick Fix: The 30-Second SMTP Check
Before diving into the full diagnostic workflow, run this quick test. It catches 70% of SMTP failures:
# Test basic SMTP connectivity
nc -zv your-smtp-server.com 587
Expected output: Connection to your-smtp-server.com port 587 [tcp/submission] succeeded!
If the connection succeeds but emails still fail, your app’s SMTP credentials are likely wrong. Check your app’s environment variables or config file for:
SMTP_HOST— should match your mail server hostnameSMTP_PORT— typically 587 (STARTTLS), 465 (SSL), or 25 (plain, often blocked)SMTP_USER/SMTP_PASSWORD— the most common source of failuresSMTP_FROM— the sender email address
If the nc command fails, your issue is at the network or server level — proceed to the root causes below.
Ops Note: Separate App Bugs from Mail Transport Bugs
Email failures are easier to fix when you test outside the app first. Confirm the server can reach the SMTP host and port, then confirm credentials, then check TLS mode, then check sender/domain policy. Only after that should you blame the application. This keeps a bad password, blocked port, or DNS problem from turning into a long app debugging session.
Root Cause 1: SMTP Server Not Running or Not Reachable
How to confirm
If you’re using a local Postfix or Sendmail on your VPS, check the service status:
# Check Postfix status
systemctl status postfix
# Check if it's listening
ss -tlnp | grep -E ':25 |:587 |:465 '
If Postfix is not running, check the logs for why:
journalctl -u postfix --since "1 hour ago" | tail -30
If you’re using an external SMTP relay (SendGrid, Mailgun, Amazon SES), test connectivity from your VPS:
# Test SMTP relay connectivity
nc -zv smtp.sendgrid.net 587
nc -zv smtp.mailgun.org 587
nc -zv email-smtp.us-east-1.amazonaws.com 587
The fix
For local Postfix:
# Start Postfix
sudo systemctl start postfix
sudo systemctl enable postfix
# If it fails to start, check for config errors
sudo postfix check
For firewall issues — many new VPS instances block outbound SMTP by default:
# Check if UFW is blocking outbound
sudo ufw status verbose
# Allow outbound SMTP (for sending)
sudo ufw allow out 587/tcp
sudo ufw allow out 465/tcp
For cloud provider SMTP blocks — some providers block port 25 entirely. Use port 587 (submission) with STARTTLS, or use an authenticated SMTP relay service.
Verify the fix
nc -zv smtp.yourprovider.com 587
# Should return "succeeded" or "Connected"
Root Cause 2: SMTP Authentication Failed
How to confirm
Check your application logs for authentication errors:
# Docker app logs
docker logs your-app-container 2>&1 | grep -i "auth|smtp|login"
# For apps storing logs to disk
grep -r "Authentication failed|SMTP.*535|535 5.7.8" /var/log/your-app/
Common error messages to watch for:
535 Authentication failed— wrong username or password534 Authentication mechanism is too weak— need stronger auth method503 AUTH command used when not advertised— server doesn’t support auth on this port
The fix
For transactional email services (SendGrid, Mailgun):
- You need an API key or SMTP password, not your regular account password
- For SendGrid: use
apikeyas the username and your API key as the password - For Mailgun: use
postmaster@yourdomain.comas the username and the SMTP password from your Mailgun dashboard - For SES: use your AWS IAM access key as the username and secret key as the password
For self-hosted Postfix with SASL auth:
# Edit Postfix SASL config
sudo postconf -e 'smtp_sasl_auth_enable = yes'
sudo postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
sudo postconf -e 'smtp_sasl_security_options = noanonymous'
# Add your SMTP credentials
echo "[smtp.sendgrid.net]:587 apikey:YOUR_SENDGRID_API_KEY" | sudo tee /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
# Reload Postfix
sudo systemctl reload postfix
For application environment variables:
Most self-hosted apps (Vaultwarden, Nextcloud, Twenty CRM, NocoDB) use environment variables for SMTP config. Double-check these common gotchas:
- Does your password contain special characters (
!,@,#,$)? In Docker Compose files, wrap values in quotes:SMTP_PASSWORD="my$ecure@Password!" - In
.envfiles, shell-special characters may need escaping - Some apps require an app-specific password rather than your main email password (for Gmail, Microsoft 365)
Verify the fix
# For Postfix relay testing
sudo sendmail -bv test@example.com
# Or use swaks (Swiss Army Knife for SMTP)
swaks --to test@example.com --server smtp.yourprovider.com:587 --auth LOGIN --auth-user youruser --auth-password yourpass
Root Cause 3: TLS/SSL Handshake or STARTTLS Failure
How to confirm
TLS errors have distinct signatures:
530 STARTTLS required— the server requires TLS but your app is sending plaintextSSL: TLS handshake failed— certificate mismatch or protocol version issueSSL routines:ssl3_get_record:wrong version number— connecting to a non-TLS port with TLS enabled (or vice versa)
Test the SMTP server’s TLS capabilities:
# Check what protocols the server offers
openssl s_client -starttls smtp -connect smtp.gmail.com:587 -servername smtp.gmail.com
# Look for: "250-STARTTLS" in the server greeting
The fix
Port and protocol mapping is the most common TLS issue. Self-hosted apps typically support two modes:
| Port | Encryption | When to Use |
|---|---|---|
| 587 | STARTTLS (upgrade from plain to TLS) | Most common. Works with all modern providers. The app connects in plaintext, then issues STARTTLS to upgrade to TLS. |
| 465 | Implicit TLS (SMTPS) | Legacy but still widely used. Connection is TLS from the start. Set SMTP_SECURE=true or SMTP_ENCRYPTION=ssl. |
| 25 | Plaintext (often no auth) | Historically the default SMTP port. Often blocked by ISPs and cloud providers. Not recommended for app-to-server delivery. |
The fix depends on your app’s SMTP library:
- If you set
SMTP_PORT=465andSMTP_ENCRYPTION=tls, some apps interpret this as STARTTLS on port 465 — which fails. SetSMTP_ENCRYPTION=sslfor port 465. - If you set
SMTP_PORT=587andSMTP_ENCRYPTION=ssl, the app tries implicit TLS on port 587 — which also fails. SetSMTP_ENCRYPTION=tlsfor port 587. - Reference your app’s documentation for the exact config key names. Common patterns:
SMTP_ENCRYPTION,SMTP_SECURE,MAIL_ENCRYPTION,MAIL_FROM_NAME.
For certificate issues: If your self-hosted app is rejecting a self-signed or Let’s Encrypt certificate, you may need to disable certificate verification — but this is a security risk. A better approach:
# Update the CA certificates bundle
sudo apt update && sudo apt install --reinstall ca-certificates
sudo update-ca-certificates --fresh
# For Docker containers, rebuild the image with updated certs
# or mount the host's CA bundle:
docker run -v /etc/ssl/certs:/etc/ssl/certs:ro your-image
Verify the fix
# Test STARTTLS on port 587
echo "EHLO test" | openssl s_client -starttls smtp -connect smtp.yourprovider.com:587 -servername smtp.yourprovider.com 2>&1 | grep "250-STARTTLS"
# Test implicit TLS on port 465
openssl s_client -connect smtp.yourprovider.com:465 -servername smtp.yourprovider.com 2>&1 | grep -E "CONNECTED|verify error"
Root Cause 4: SMTP Relay Access Denied
How to confirm
Error messages include:
554 Relay access denied550 #5.7.1 Relaying denied553 sorry, that domain isn't in my list of allowed rcpthosts
Your local Postfix server is receiving the email but refusing to relay it to the destination. This typically means Postfix doesn’t know it should accept mail from your app.
The fix
If your app and Postfix are on the same server:
# Only needed if Postfix rejects local submissions
sudo postconf -e 'mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128'
sudo systemctl reload postfix
If your app runs in Docker and Postfix runs on the host:
# Add the Docker bridge network to Postfix's trusted networks
sudo postconf -e 'mynetworks = 127.0.0.0/8 172.17.0.0/16'
sudo systemctl reload postfix
If using a hosted SMTP relay: Ensure your VPS IP is whitelisted. SendGrid, Mailgun, and SES all have IP whitelist features in their dashboards. Without whitelisting, the relay will reject your connection even with valid credentials.
Verify the fix
# Send a test email via command line
echo "Subject: Test" | sendmail -v test@example.com
# Or from a Docker container:
docker exec your-container sendmail -v test@example.com
Root Cause 5: Timeout or DNS Resolution Failure
How to confirm
Connection timed out— the SMTP server isn’t respondingName or service not known— DNS can’t resolve the SMTP hostnameNetwork is unreachable— no route to the SMTP server
Diagnose with:
# DNS resolution check
dig smtp.sendgrid.net
# Should return an A record
# Routing check
traceroute -n smtp.sendgrid.net
# Should show a path to the destination
# Direct connection with longer timeout
nc -zvw 10 smtp.sendgrid.net 587
# -w 10 = 10 second timeout instead of default
The fix
DNS issues:
# Force DNS resolution with a different resolver
echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf
# Or in Docker, add DNS config to docker-compose.yml:
# dns:
# - 8.8.8.8
# - 1.1.1.1
Timeout issues:
Many self-hosted apps have a default SMTP timeout of 5-10 seconds. If your VPS has high latency to the SMTP server, this can cause spurious failures:
- Check the round-trip time:
ping -c 5 smtp.yourprovider.com - Increase the app’s SMTP timeout setting if available (Vaultwarden:
SMTP_TIMEOUT=30) - Consider using an SMTP relay closer to your VPS’s data centre (e.g., if your VPS is in Toronto, use a relay with Canadian endpoints)
Firewall issues in Docker:
Docker containers use the host’s network stack for outbound connections by default, but custom Docker networks (like docker-compose.yml networks) can block outbound SMTP:
# Test from inside the container
docker exec your-container nc -zv smtp.sendgrid.net 587
# If fails, check the Docker network's iptables rules
iptables -L DOCKER-USER -n 2>/dev/null | grep smtp
# Workaround: use host networking for the container
# network_mode: "host" in docker-compose.yml
Verify the fix
# Quick end-to-end test from the app's environment
docker exec your-container bash -c "echo 'Test email from container' | mail -s 'SMTP Test' test@example.com"
Root Cause 6: Rate Limiting or Quota Exceeded
How to confirm
454 Throttling failure: Monthly quota exceeded550 5.7.0 Too many emails from your IP421 Too many connections from your IP- Emails that worked yesterday but fail today, with no config changes
Check your transactional email provider’s dashboard for usage statistics. Most providers (SendGrid, Mailgun) offer free tiers with limits:
- SendGrid: 100 emails/day free, then 300/month free plan
- Mailgun: 100 emails/day for the first 30 days, then pay-as-you-go
- Amazon SES: 62,000 emails/month from EC2 or 2,000/day from other AWS services
The fix
Check your daily/monthly usage:
- SendGrid dashboard ? Stats ? Email Activity
- Mailgun dashboard ? Analytics ? Usage
- SES console ? Account Dashboard ? Sending Statistics
If quota is the issue, you have three options:
- Upgrade your transactional email plan
- Queue non-urgent emails and send them in batches under the daily limit
- Set up your own outbound mail server with Postfix for low-volume sending (under 1,000 emails/day typically won’t trigger reputation issues on a clean IP)
For rate limiting (too many emails too fast):
# Add a queuing mechanism to your app's mailer
# Most frameworks (Laravel, Symfony, Django) support mail queues:
# php artisan queue:work
# python manage.py qcluster
Verify the fix
# After waiting or upgrading, send a test from the app's interface
# Trigger a password reset or test notification
Diagnostic Flowchart: Which Root Cause Should You Check First?
- Can you reach the SMTP server? (nc -zv smtp.server.com 587)
- No ? Check Root Cause 1 (server not running/firewall) or Root Cause 5 (DNS/timeout)
- Yes ? Go to step 2
- Does the SMTP server accept your credentials? (Check app logs for auth errors)
- No ? Check Root Cause 2 (authentication failed)
- Yes ? Go to step 3
- Does the connection negotiate TLS correctly? (openssl s_client -starttls smtp)
- No ? Check Root Cause 3 (TLS/STARTTLS failure)
- Yes ? Go to step 4
- Does the server accept the relay? (Check for relay denied errors)
- No ? Check Root Cause 4 (relay access denied)
- Yes ? Go to step 5
- Are you hitting rate or quota limits? (Check provider dashboard)
- Yes ? Check Root Cause 6 (rate limiting)
- No ? Check the specific app’s mailer configuration and debug logs
— the issue may be in how the app formats or queues emails
Prevention: Setting Up Reliable Email Delivery
Once your SMTP configuration is working, take three steps to prevent future issues:
- Set up email monitoring. Configure your app to log all SMTP transactions. Most self-hosted apps have a mail log or debug mode — enable it. For Docker-based apps, use
docker logs -fto watch email delivery in real time during testing. - Configure SPF, DKIM, and DMARC. Even if your SMTP connection works, recipient servers may reject or spam-filter your emails without proper authentication. Follow the Email Delivery Problems guide for DNS record setup.
- Document your SMTP configuration. Save your working
.envordocker-compose.ymlSMTP settings to a secure location. Include the provider name, port, encryption type, and a note about any IP whitelisting or special credentials. This saves hours of re-diagnosis when you migrate or rebuild the server.
When to Use a Transactional Email Service vs. Postfix/Sendmail
| Setup | When to Use | When to Avoid |
|---|---|---|
| Local Postfix | Low volume (under 500/day), single server, full control needed | IP reputation issues on a new VPS; cloud providers blocking port 25 |
| SendGrid / Mailgun / SES | Production apps, any volume, reliable delivery, no IP management | Very high volume (cost adds up); strict data sovereignty requirements (use Canadian provider) |
| msmtp in containers | Minimal Docker containers, simple relay, no Postfix overhead | Complex delivery rules, multiple domains, need for queue management |
| Hosted Email (cPanel, CWH Hosted Email) | Business email on your domain, with SMTP access for apps | High-volume transactional sending (message/day limits apply) |
When to Escalate to Hosting Support
Some SMTP issues are outside the app’s configuration. Contact your hosting provider when:
- Your VPS cannot establish any outbound connection to remote SMTP servers on any port (blocked by provider firewall)
- Your VPS IP is on an email blacklist even after fixing configuration (check here)
- You need a reverse DNS (PTR) record set for your VPS IP — required by many recipient mail servers
- You need a dedicated IP with clean reputation for high-volume sending
At Canadian Web Hosting, our Managed Support team handles SMTP diagnostics, PTR record requests, and firewall configuration. If you’re using our Hosted Email or Microsoft 365 plans, SMTP credentials and ports are provided in your welcome email — and our support team can verify they’re working in your application.
Related Issues
- Docker Logs Are Filling Your Server Disk: Diagnosis and Fix — If your app generates excessive log output during SMTP debugging, here’s how to manage Docker log rotation.
- Your Self-Hosted App Won’t Start: A Systematic Guide to Diagnosing Docker Deployment Failures — If your container isn’t even starting, start with this guide before troubleshooting SMTP.
- Email Delivery Problems: Fixing Bounced Emails from Your Server — Once SMTP is working, if emails still don’t arrive, configure SPF, DKIM, and DMARC to pass authentication checks.
What You’ll Need
To follow this guide, you’ll need a server running your self-hosted applications. We recommend a Cloud VPS with root access — you’ll need it to check firewalls, service status, and system logs. If you’re using Docker-based apps, any CWH Cloud VPS plan with Docker installed will work.
For SMTP relay, you can use a free transactional email service tier (SendGrid, Mailgun) or set up Postfix locally. If you prefer a managed solution, our Hosted Email plan includes SMTP/IMAP access for your applications.
Sources and Command Notes
This SMTP troubleshooting guide was refreshed in June 2026. The network diagnostic tools, Postfix configuration check, and UFW outbound rule syntax were smoke-tested in Ubuntu 24.04 where practical. Exact relay names, ports, and authentication modes depend on your mail provider.
Be First to Comment