PHP mail does not get sent, how do I debug?
Stefan Bogdanescu
Founder & Senior Architect
PHP Mail Fails on Server: A Senior Developer's Guide to Debugging Email Delivery Issues
Sending emails is a fundamental requirement for almost any web application, whether it’s registration confirmations, password resets, or invitations. When you are developing locally and everything works perfectly, only to encounter mysterious failures when deploying to a live server, it immediately triggers debugging headaches.
The scenario you’ve described—where system mail commands work in the terminal but PHP's mail() function fails silently on the server—is extremely common. This almost always points to a mismatch between how the PHP environment is configured and how the underlying Mail Transfer Agent (MTA), like Postfix or Sendmail, is set up on the actual server.
As a senior developer, I can tell you that debugging this requires looking beyond just the PHP code and investigating the entire stack: the operating system configuration, service permissions, and mail delivery protocols.
Here is a comprehensive guide on how to debug why your PHP emails are not being sent on the server.
Understanding the Discrepancy: Local vs. Server Environment
The fact that mail example@example.com works in the terminal but $res = mail(...) fails in PHP reveals a critical distinction:
- Terminal Command Success: This confirms that your operating system (Linux/Unix) has a functional Mail Transfer Agent (MTA), like Postfix, installed and capable of sending outbound mail through the network.
- PHP Failure: This indicates that the specific mechanism PHP uses to interface with the MTA—often relying on PHP's
mail()function or configured external system calls—is failing to properly hand off the message for delivery, even if the underlying MTA exists.
The failure is rarely about the email content itself; it’s usually a configuration, permission, or environment variable issue.
Step-by-Step Debugging Strategy
To fix this, we must systematically check the layers between PHP and the mail server.
1. Check PHP Configuration (php.ini)
The first place to look is your PHP configuration file. If you are relying on PHP’s native mail() function, it often relies on external binaries being correctly linked. For robust email handling in modern applications, many developers move away from the basic mail() function and use dedicated libraries or services. While foundational concepts like those seen in building scalable systems, such as those discussed at laravelcompany.com, emphasize environment consistency, this principle applies directly to email delivery.
Ensure that PHP is configured correctly to communicate with the system mail tools. Look for settings related to sendmail_path or similar variables if you are using custom configurations.
2. Verify MTA Status and Permissions
Since you mentioned installing Postfix, we need to confirm its operational status:
- Service Check: Ensure the Postfix service is actively running on the server:
sudo systemctl status postfix - Mail Logs: Check the mail logs for errors. These logs often reveal exactly why a message was rejected (e.g., rejected by local filters, failed relay).
sudo tail -f /var/log/mail.log - Permissions: The user account running the PHP process (often
www-dataorapache) must have the necessary read/write permissions to access the mail spool directories and execute the required binaries.
3. Alternative: Use a Dedicated Library or Service
Relying solely on PHP's native mail() function is often brittle in production environments because it bypasses sophisticated error handling and security protocols. A more robust approach, especially when dealing with complex delivery requirements (like DKIM signing or high deliverability), is to use an established library or a dedicated transactional email service (like SendGrid, Mailgun, or AWS SES).
If you are building a large system, integrating these external services provides better control and significantly simplifies debugging because the failure point shifts from your server configuration to the external API response.
Conclusion: Moving Towards Robust Delivery
Debugging mail failures is less about finding a single bug and more about ensuring end-to-end communication reliability. If you are stuck between local success and server failure, stop focusing only on the mail() function call. Instead, treat this as an infrastructure problem.
Always verify that your MTA is running correctly, check file permissions for the web server user, and consider migrating to a dedicated email delivery solution if you require enterprise-grade reliability. By following this layered approach—checking PHP settings, system services, and file permissions—you will isolate the root cause and get those emails delivered reliably.