PHPMailer — Send Email in PHP: Tutorials, Examples and Reference

Updated:

PHPMailer is the most widely used library for sending email from PHP. It wraps PHP’s mail() function and SMTP in a clean, object-oriented API that handles the parts that are easy to get wrong: MIME encoding, attachments, HTML and plain-text alternatives, character sets, SMTP authentication and TLS.

This domain hosted the original PHPMailer website and its documentation for over a decade. The library itself is now developed and maintained on GitHub at github.com/PHPMailer/PHPMailer. That repository is the authoritative source for releases and issue tracking. The pages here are tutorials, worked examples and a reference for day-to-day use.

Install PHPMailer

Composer is the supported installation method:

composer require phpmailer/phpmailer

The package requirements depend on the release you install. Check Composer compatibility with your supported PHP runtime before upgrading. If you cannot use Composer, you can download the source and require the three main class files manually. See Installing PHPMailer for that route, along with the extensions you need (ext-openssl for TLS, ext-mbstring for non-ASCII content).

Send an email over authenticated SMTP

This is the example most people are looking for. It sends an HTML message with a plain-text alternative and one attachment, over STARTTLS on port 587:

<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true); // true = throw exceptions

try {
    $mail->isSMTP();
    $mail->Host       = 'smtp.example.com';
    $mail->SMTPAuth   = true;
    $mail->Username   = 'user@example.com';
    $mail->Password   = 'your-password';
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;

    $mail->setFrom('from@example.com', 'Your App');
    $mail->addAddress('recipient@example.com', 'Recipient Name');
    $mail->addReplyTo('support@example.com', 'Support');

    $mail->addAttachment('/path/to/invoice.pdf', 'invoice.pdf');

    $mail->isHTML(true);
    $mail->Subject = 'Your invoice is ready';
    $mail->Body    = '<p>Hello, your invoice is <b>attached</b>.</p>';
    $mail->AltBody = 'Hello, your invoice is attached.';

    $mail->send();
    echo 'Message sent.';
} catch (Exception $e) {
    echo "Send failed: {$mail->ErrorInfo}";
}

Two details matter. Passing true to the constructor makes PHPMailer throw exceptions instead of returning false, which is what you want in any real application. And $mail->ErrorInfo carries the useful SMTP-level message. The exception message on its own is usually too generic to debug with.

Ports and encryption

Port Constant Notes
587 ENCRYPTION_STARTTLS The usual choice. Connection starts plain, then upgrades to TLS.
465 ENCRYPTION_SMTPS Implicit TLS from the first byte. Still widely supported.
25 none Server-to-server relay. Blocked outbound by most hosts and cloud providers.

Mismatching the port and the constant is the single most common cause of SMTP connect() failed. Port 587 with ENCRYPTION_SMTPS will hang; port 465 with ENCRYPTION_STARTTLS will fail immediately.

Send through Gmail

Gmail no longer accepts your account password over SMTP. You need either an App Password (requires 2-Step Verification on the account) or XOAUTH2. With an App Password, the only changes are the host and the credentials:

$mail->isSMTP();
$mail->Host       = 'smtp.gmail.com';
$mail->SMTPAuth   = true;
$mail->Username   = 'you@gmail.com';
$mail->Password   = 'abcd efgh ijkl mnop'; // 16-character App Password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port       = 587;

Gmail rewrites the From header to the authenticated account unless the address is a verified alias, so set setFrom() to the same mailbox you authenticate with. For anything beyond low volume, use a transactional provider rather than Gmail. Daily sending limits and account policies apply; inspect the SMTP response when a submission fails.

Send with mail() instead of SMTP

If a local MTA is configured, you can skip SMTP entirely. PHPMailer defaults to PHP’s mail(), so omitting isSMTP() is enough:

$mail = new PHPMailer(true);
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('recipient@example.com');
$mail->Subject = 'Hello';
$mail->Body    = 'Plain text body';
$mail->send();

This is simpler but gives you almost no diagnostics: mail() returns success as soon as the message is queued locally, so a message that the MTA later drops looks like a successful send. SMTP is worth the extra configuration for anything transactional. See the mail() example for the full comparison.

Debugging a failed send

Turn on protocol-level output before anything else. It shows the actual SMTP conversation, which almost always identifies the problem on the first run:

use PHPMailer\PHPMailer\SMTP;

$mail->SMTPDebug = SMTP::DEBUG_SERVER; // client and server messages

Common failures and what they actually mean:

  • SMTP connect() failed — the TCP connection never completed. Wrong host or port, an outbound firewall, or a port/encryption mismatch. Test reachability first: openssl s_client -starttls smtp -connect smtp.example.com:587.
  • Could not authenticate — credentials rejected. On Gmail this nearly always means a regular password was used instead of an App Password.
  • certificate verify failed — the server’s certificate could not be validated, usually a stale or missing CA bundle. Fix openssl.cafile in php.ini. Do not disable verification in production; it silently removes the protection TLS is there to provide.
  • Message sends but lands in spam. Check SPF, DKIM and DMARC, as well as sender reputation, recipient policy and the message itself.

Domain authentication and deliverability

Successful SMTP submission does not guarantee inbox delivery. Reputation, recipient policy and message content matter, alongside DNS authentication for your sending domain: an SPF record listing who may send, a DKIM key so the message can be cryptographically verified, and a DMARC policy telling receivers what to do when the first two fail. Investigate authentication results and provider feedback before attributing filtering to one cause.

PHPMailer can sign messages with DKIM itself using the DKIM_domain, DKIM_selector and DKIM_private properties, which is useful when sending through a host that does not sign for you.

Reference and guides

Frequently asked

Is PHPMailer still maintained?

Yes. Development happens at github.com/PHPMailer/PHPMailer, where releases and security advisories are published. Check the official releases and your Composer lock file for the version in use; the upstream installation instructions now target the 7.x series.

PHPMailer or Symfony Mailer?

If you are already in a Symfony or Laravel application, use the framework’s mailer. It is wired into the container, queues and templating. PHPMailer suits standalone scripts, legacy codebases and projects with no framework, where a single dependency that just sends email is the right size of tool.

Can it send to many recipients at once?

It can, but reuse one SMTP connection with $mail->SMTPKeepAlive = true; and call clearAddresses() between messages rather than constructing a new object each time. Sending one message with hundreds of addresses in To exposes every recipient to the others; loop and send individually instead.

Which PHP version do I need?

Use a supported PHP release and check the requirements of the PHPMailer version selected by Composer. Use ext-openssl for TLS connections and ext-mbstring if you send non-ASCII subjects or bodies.

Troubleshooting and in-depth guides

The reference pages above cover the normal path. These guides cover what happens when it does not work, and the delivery layer that sits underneath every message you send.

When PHPMailer throws an error

Connecting to a specific provider

Getting the message accepted and delivered

Writing better mail with PHPMailer