A complete set of working PHPMailer examples, one per sending transport and message type. Every snippet below is self-contained: install PHPMailer, paste, change the addresses and credentials, run. All examples target PHPMailer 6.x.
These pages replace the example scripts that were distributed with the original PHPMailer package (test_mail_basic.php, test_smtp_advanced.php and the rest). The library itself is maintained at github.com/PHPMailer/PHPMailer, where the current example folder also lives.
Before you start
composer require phpmailer/phpmailer
Every example assumes these four lines at the top of the file:
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
Passing true to the constructor turns on exceptions. Without it, send() returns false and you have to remember to check it — which is how silent email failures happen in production.
1. Basic mail()
The simplest possible send. Uses PHP’s built-in mail() function, which hands the message to whatever MTA is configured locally.
$mail = new PHPMailer(true);
try {
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('recipient@example.com', 'Recipient Name');
$mail->Subject = 'Basic mail() test';
$mail->Body = 'This is a plain-text message sent with PHP mail().';
$mail->send();
echo 'Sent.';
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
mail() is the default transport, so there is nothing to configure. The catch: it reports success as soon as the message is accepted locally. If the MTA drops it thirty seconds later, your code never finds out. Fine for a contact form on a well-configured server, wrong for anything transactional.
2. Advanced mail() — HTML, attachment, envelope sender
$mail = new PHPMailer(true);
try {
$mail->CharSet = PHPMailer::CHARSET_UTF8;
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('recipient@example.com');
$mail->addReplyTo('support@example.com', 'Support');
$mail->Sender = 'bounces@example.com'; // envelope sender / Return-Path
$mail->addAttachment('/path/to/invoice.pdf', 'invoice.pdf');
$mail->isHTML(true);
$mail->Subject = 'Your invoice';
$mail->Body = '<p>Hello,</p><p>Your invoice is <b>attached</b>.</p>';
$mail->AltBody = "Hello,\n\nYour invoice is attached.";
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
Set CharSet explicitly whenever the subject or body can contain accents or non-Latin characters — the default is ISO-8859-1 and will mangle them. Sender sets the envelope sender, which is where bounces go; it is separate from the visible From header.
3. Sendmail transport
Talks to the sendmail binary directly instead of going through mail(). Slightly more control, and it works when mail() is disabled.
$mail = new PHPMailer(true);
$mail->isSendmail();
$mail->Sendmail = '/usr/sbin/sendmail -bs'; // -bs = read SMTP on stdin
try {
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('recipient@example.com');
$mail->Subject = 'Sendmail test';
$mail->Body = 'Sent via the sendmail binary.';
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
Use -bs rather than -t. With -bs, sendmail speaks SMTP over stdin and PHPMailer gets a real status back; with -t it parses headers and stays silent about failures. If you are on Postfix or Exim, the sendmail-compatible wrapper is still at that path.
4. Basic SMTP
The recommended transport for anything that matters. This is the one you want if the message must actually arrive.
$mail = new PHPMailer(true);
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');
$mail->Subject = 'SMTP test';
$mail->Body = 'Sent through authenticated SMTP.';
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
Port and encryption pairs
| Port | Constant | Behaviour |
|---|---|---|
| 587 | ENCRYPTION_STARTTLS |
Connect in plain text, then upgrade to TLS. The modern default. |
| 465 | ENCRYPTION_SMTPS |
TLS from the first byte. Still supported everywhere. |
| 25 | none | Server-to-server relay. Blocked outbound by nearly every host. |
Crossing them is the most common configuration error there is: 587 with ENCRYPTION_SMTPS hangs until timeout, 465 with ENCRYPTION_STARTTLS fails immediately.
5. Advanced SMTP — HTML, inline image, multiple recipients
$mail = new PHPMailer(true);
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->CharSet = PHPMailer::CHARSET_UTF8;
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('first@example.com', 'First Recipient');
$mail->addCC('cc@example.com');
$mail->addBCC('archive@example.com');
$mail->addReplyTo('support@example.com', 'Support');
$mail->addEmbeddedImage('/path/to/logo.png', 'logo');
$mail->addAttachment('/path/to/report.csv', 'report.csv');
$mail->isHTML(true);
$mail->Subject = 'Your monthly report';
$mail->Body = '<img src="cid:logo" alt="Logo"><h1>Monthly report</h1>'
. '<p>The full data is attached as CSV.</p>';
$mail->AltBody = "Monthly report\n\nThe full data is attached as CSV.";
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
Inline images work by content ID: addEmbeddedImage($path, $cid) then <img src="cid:$cid"> in the body. The alternative — msgHTML() — parses your HTML, embeds local images automatically and generates AltBody for you:
$mail->msgHTML(file_get_contents('email.html'), __DIR__);
6. SMTP without authentication
For an internal relay or a local MTA listening on 25 that accepts your host without credentials:
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'localhost';
$mail->SMTPAuth = false;
$mail->Port = 25;
$mail->SMTPAutoTLS = false; // do not attempt STARTTLS on a plain local relay
try {
$mail->setFrom('from@example.com', 'Your App');
$mail->addAddress('recipient@example.com');
$mail->Subject = 'Relay test';
$mail->Body = 'Sent through an unauthenticated relay.';
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
SMTPAutoTLS defaults to true, so PHPMailer will try to upgrade to TLS whenever the server advertises STARTTLS. On a relay with a self-signed or absent certificate that upgrade fails and takes the send with it — hence turning it off here. Only do this for traffic that never leaves your own network.
7. Gmail — basic
Gmail stopped accepting account passwords over SMTP. You need an App Password, which requires 2-Step Verification to be enabled on the account. Once you have the 16-character value, nothing else changes:
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->Username = 'you@gmail.com';
$mail->Password = 'abcdefghijklmnop'; // App Password, not your login password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('you@gmail.com', 'Your Name');
$mail->addAddress('recipient@example.com');
$mail->Subject = 'Gmail SMTP test';
$mail->Body = 'Sent through Gmail SMTP.';
$mail->send();
} catch (Exception $e) {
echo 'Failed: ' . $mail->ErrorInfo;
}
Set setFrom() to the same mailbox you authenticate with. Gmail rewrites the From header to the authenticated account unless the address is a verified alias, so a mismatch just gets silently overwritten.
8. Gmail — advanced, with XOAUTH2
App Passwords are simple but tied to one account and revocable by an admin. For a service that sends on behalf of Google Workspace users, XOAUTH2 is the supported path. It needs one extra package:
composer require league/oauth2-google
use PHPMailer\PHPMailer\OAuth;
use League\OAuth2\Client\Provider\Google;
$provider = new Google([
'clientId' => 'YOUR_CLIENT_ID',
'clientSecret' => 'YOUR_CLIENT_SECRET',
]);
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->AuthType = 'XOAUTH2';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
$mail->setOAuth(new OAuth([
'provider' => $provider,
'clientId' => 'YOUR_CLIENT_ID',
'clientSecret' => 'YOUR_CLIENT_SECRET',
'refreshToken' => 'YOUR_REFRESH_TOKEN',
'userName' => 'you@yourdomain.com',
]));
You obtain the refresh token once, through Google’s OAuth consent flow, and store it. PHPMailer exchanges it for a short-lived access token on each send. Gmail’s daily sending limits still apply either way — for real volume, use a transactional provider.
9. Sending many messages on one connection
Opening a fresh SMTP connection per message is slow and looks like abuse to the receiving server. Keep the connection alive and reset the recipients between sends:
$mail = new PHPMailer(true);
$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->SMTPKeepAlive = true; // reuse the connection
$mail->setFrom('from@example.com', 'Your App');
$mail->Subject = 'Your weekly digest';
foreach ($subscribers as $subscriber) {
try {
$mail->addAddress($subscriber['email'], $subscriber['name']);
$mail->Body = renderDigest($subscriber);
$mail->send();
} catch (Exception $e) {
error_log("Failed for {$subscriber['email']}: {$mail->ErrorInfo}");
} finally {
$mail->clearAddresses(); // essential
}
}
$mail->smtpClose();
clearAddresses() in a finally block is not optional. Without it, recipients accumulate and message five goes to all five people. Use clearAttachments() too if attachments vary per message.
Never put a large recipient list in a single To — every recipient sees every other address. Loop and send individually.
10. Validating addresses before sending
if (!PHPMailer::validateAddress($email)) {
throw new InvalidArgumentException("Invalid address: $email");
}
Cheap, and it stops one malformed address from aborting a batch. It validates syntax only — it cannot tell you whether the mailbox exists.
Debugging any of the above
$mail->SMTPDebug = SMTP::DEBUG_SERVER; // client + server messages
This prints the actual SMTP conversation and identifies the problem on the first run in almost every case. The levels are DEBUG_OFF, DEBUG_CLIENT, DEBUG_SERVER, DEBUG_CONNECTION and DEBUG_LOWLEVEL. Start at DEBUG_SERVER. Always turn it off in production — it writes straight to output.
Where to go next
- PHPMailer Tutorial — the same ground, step by step, if you are starting from scratch.
- Installing PHPMailer — Composer and manual installation, required extensions.
- SMTP Example — SMTP configuration in detail, including TLS options.
- mail() Example — the local transport and when it is the right choice.
- Methods Reference — signatures for every method used above.
- Properties Reference — every configurable property in 6.x.
- Extending PHPMailer — subclassing so this configuration lives in one place.
If messages send successfully but land in spam, none of the code above is the problem: that is SPF, DKIM and DMARC on your sending domain. PHPMailer can sign with DKIM itself through the DKIM_domain, DKIM_selector and DKIM_private properties.
