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 PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerSMTP;
use PHPMailerPHPMailerException;
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,nnYour 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 reportnnThe 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 PHPMailerPHPMailerOAuth;
use LeagueOAuth2ClientProviderGoogle;
$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.
Going further than the examples
- PHPMailer with Gmail in 2026 — the current state of the Gmail examples.
- Sending bulk email — SMTPKeepAlive, throttling and queues.
- Embedding images in HTML email — CID, base64 and hosted.
- Testing email locally — running these examples without sending real mail.
- PHPMailer with Microsoft 365 — the modern equivalent of the old Exchange example.
