Email Sending with PHP
Learn how PHP's built-in mail() function works, why real projects usually rely on a library or transactional email service instead.
Introduction
Sending email is a common requirement in web applications: welcome messages after registration, password reset links, order confirmations, and notifications all rely on it. PHP has shipped a built-in mail() function since its earliest versions, but modern applications rarely use it directly.
This lesson covers what mail() does, why it falls short of what real projects need, and what most PHP developers reach for instead.
- How PHP's built-in mail() function works.
- The practical limitations that make mail() unsuitable for most real projects.
- Why libraries like PHPMailer are the common alternative.
- What transactional email services like SendGrid and Mailgun provide.
- The general shape of sending an email through a library, conceptually.
The Built-in mail() Function
PHP's mail() function sends a plain email using whatever mail transport is configured on the server it runs on. It takes a recipient, a subject, a message body, and optional headers.
<?php
$to = 'someone@example.com';
$subject = 'Welcome to PrograMinds';
$message = 'Thanks for signing up!';
$headers = 'From: no-reply@programinds.example';
$sent = mail($to, $subject, $message, $headers);
if ($sent) {
echo 'Email sent.';
} else {
echo 'Email could not be sent.';
}On paper this looks simple, and on a properly configured Linux server with a working mail transfer agent (like sendmail or postfix), it can work. In practice, that configuration step is where most of the trouble starts.
Limitations of mail()
Unreliable Deliverability
Emails sent via mail() are easily flagged as spam, since they typically lack proper authentication (SPF, DKIM, DMARC) that inbox providers check for.
No Attachments Out of the Box
Sending a file attachment requires manually building a MIME multipart message — mail() offers no built-in help for this.
No Easy HTML Support
Sending a nicely formatted HTML email means manually crafting the right headers and content type yourself.
Requires Server Mail Configuration
mail() depends entirely on a correctly configured local mail server, which many hosting environments (including most local development setups) simply do not have.
On a typical local development machine, calling mail() usually does nothing useful — there is no mail server installed to actually hand the message off to, so the function may return false or silently fail to deliver anything.
Why Use a Library or Service Instead
Because of these limitations, most real PHP projects avoid mail() entirely. Instead, they use one of two approaches: a library like PHPMailer that talks directly to an SMTP server with proper authentication, or a transactional email service that handles delivery, formatting, and deliverability concerns entirely.
PHP's mail()
- No SMTP authentication support
- Depends on local server mail configuration
- No built-in HTML or attachment handling
- Poor deliverability in practice
PHPMailer / Email Services
- Connects to SMTP with proper authentication
- Works the same regardless of server configuration
- Built-in support for HTML bodies and attachments
- Better deliverability, tracking, and error reporting
Conceptual Example: Sending with a Library
You do not need real SMTP credentials to understand the shape of this code. A library like PHPMailer is installed via Composer (covered in the next lesson) and used roughly like this — notice how it separates the recipient, subject, body, and sender clearly.
<?php
require 'vendor/autoload.php';
use PHPMailer\PHPMailer\PHPMailer;
$mail = new PHPMailer();
$mail->setFrom('no-reply@programinds.example', 'PrograMinds');
$mail->addAddress('someone@example.com');
$mail->Subject = 'Welcome to PrograMinds';
$mail->isHTML(true);
$mail->Body = '<h1>Welcome!</h1><p>Thanks for signing up.</p>';
if ($mail->send()) {
echo 'Email sent successfully.';
} else {
echo 'Email could not be sent: ' . $mail->ErrorInfo;
}Every email-sending approach, whether mail(), PHPMailer, or a hosted API, ultimately deals with the same four ideas: who it is from, who it is going to, the subject, and the body. Once you recognize that shape, switching between tools becomes mostly a matter of syntax.
Transactional Email Services
Services like SendGrid and Mailgun go a step further than a library: instead of your server sending mail directly, you make an API call and the service handles delivery, retries, spam-avoidance, and analytics like open and click tracking on your behalf.
SendGrid
A widely used transactional and marketing email API with generous free tiers for small projects.
Mailgun
A developer-focused email API popular for transactional email in web applications.
PHPMailer
A mature PHP library for composing and sending email over SMTP, without requiring a separate paid service.
The right choice depends on the project: PHPMailer with your own SMTP credentials is fine for many applications, while a transactional service is often preferred at scale, where deliverability and analytics become important.
Common Mistakes
- Relying on mail() for anything important, like password reset emails, without testing deliverability first.
- Forgetting that a local development machine usually has no mail server configured at all.
- Building HTML emails by hand-writing MIME headers instead of letting a library handle it.
- Hardcoding SMTP credentials directly into source code instead of environment variables or configuration files.
- Assuming an email "sent" without error also means it was actually delivered to the inbox.
Best Practices
- Use mail() only for the simplest, least critical use cases, if at all.
- Prefer a library like PHPMailer or a transactional service for anything users depend on, like password resets.
- Keep SMTP credentials and API keys out of source code, using environment variables instead.
- Always check the return value or response of an email-sending call, and handle failures gracefully.
- Test email sending in a safe environment (like a mail-catching tool) before sending to real users.
Frequently Asked Questions
Why did mail() return false or seem to do nothing on my machine?
Most local development environments do not have a configured mail transfer agent, so there is nothing for mail() to hand the message to. This is expected, and it is one of the main reasons libraries and services are preferred.
Do I need to buy something to use PHPMailer?
No, PHPMailer itself is free and open source. You still need access to an SMTP server (your own, your host's, or a free-tier email service) to actually deliver messages.
What is the difference between PHPMailer and a service like SendGrid?
PHPMailer is a library that sends email through SMTP credentials you provide. SendGrid and similar services are hosted platforms you call over an API, and they manage the actual delivery infrastructure for you.
Can I send HTML emails with attachments using mail()?
Technically yes, but you would have to manually construct MIME multipart headers and encode attachments yourself, which is exactly the tedious, error-prone work that libraries like PHPMailer exist to handle.
Key Takeaways
- PHP's built-in mail() function sends email using the server's configured mail transport.
- mail() has real limitations: unreliable deliverability, no built-in HTML or attachment support, and a dependency on server configuration.
- Libraries like PHPMailer connect to SMTP with authentication and handle HTML bodies and attachments cleanly.
- Transactional email services like SendGrid and Mailgun handle delivery, retries, and analytics via an API.
- Every email-sending approach shares the same core shape: sender, recipient, subject, and body.
Summary
PHP can send email out of the box with mail(), but real applications almost always outgrow it quickly due to deliverability and formatting limitations. Libraries like PHPMailer and transactional services like SendGrid fill that gap.
To actually install a library like PHPMailer in a project, you need a package manager — which is exactly what the next lesson, Composer, introduces.
- You understand what PHP's mail() function does and does not do well.
- You can explain why most projects use a library or email service instead.
- You have seen the conceptual shape of sending email through PHPMailer.
- You know the difference between a library and a hosted transactional email service.