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

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

PHPMailer 6.x requires PHP 5.5 or later and works on all current PHP versions. 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 PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerException;

require 'vendor/autoload.php';

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

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

    $mail->setFrom('[email protected]', 'Your App');
    $mail->addAddress('[email protected]', 'Recipient Name');
    $mail->addReplyTo('[email protected]', '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   = '[email protected]';
$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 apply, and they are enforced silently.

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('[email protected]', 'Your App');
$mail->addAddress('[email protected]');
$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 PHPMailerPHPMailerSMTP;

$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 — not a PHPMailer problem. It is authentication at the domain level: SPF, DKIM and DMARC records for the sending domain.

Deliverability is a DNS problem, not a code problem

PHPMailer will hand a correctly formed message to your SMTP server every time. Whether the recipient’s provider accepts it depends on records published in DNS 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. Missing DKIM is the usual reason a technically valid message is filtered.

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. Version 6.x is the current line.

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?

PHPMailer 6.x supports PHP 5.5 and above, including current releases. Use ext-openssl for TLS connections and ext-mbstring if you send non-ASCII subjects or bodies.