PHPMailer 22,298 (github.com/PHPMailer/PHPMailer (https://github.com/PHPMailer/PHPMailer 22,298 ), LGPL-2.1, 7.1.1 of 18 May 2026) builds the MIME tree, encodes headers and attachments, and speaks SMTP itself, with authentication, TLS and optional DKIM signing (the DKIM_* properties). No MTA is needed.
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use PHPMailer\PHPMailer\{PHPMailer, SMTP};
$mail = new PHPMailer(exceptions: true); // throw instead of returning false
$mail->isSMTP();
$mail->Host = '127.0.0.1';
$mail->Port = 1019; // production: 587 with SMTPSecure 'tls', SMTPAuth and Username
$mail->SMTPDebug = SMTP::DEBUG_SERVER; // print both sides of the conversation
$mail->setFrom('orders@example.com', 'Bookshop');
$mail->addAddress('reader@example.com');
$mail->Subject = 'Order #1001 has shipped';
$mail->msgHTML('<p>Your order <b>#1001</b> has shipped.</p>'); // also derives AltBody
$mail->send();Output
$ php phpmailer.php | cut -c21- | grep -E 'DATA|354|250 2.0' CLIENT -> SERVER: DATA SERVER -> CLIENT: 354 Start mail input; end with <CR><LF>.<CR><LF> SERVER -> CLIENT: 250 2.0.0 Ok: queued as 7l5aVpghyzX2aTPMHxqSEG
After EHLO, MAIL FROM and one RCPT TO per recipient, the client sends the message after DATA and ends it with a lone dot. The final 250 means the server accepted responsibility, which mail() never reports. A refused login or recipient throws PHPMailer\PHPMailer\Exception; log it and retry from a queue (Storage, Mail, Notifications). Set SMTPDebug to 0 in production.