181        'exim' => 
'/[\d]{3} OK id=(.*)/',
 
  182        'sendmail' => 
'/[\d]{3} 2.0.0 (.*) Message/',
 
  183        'postfix' => 
'/[\d]{3} 2.0.0 Ok: queued as (.*)/',
 
  184        'Microsoft_ESMTP' => 
'/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
 
  185        'Amazon_SES' => 
'/[\d]{3} Ok (.*)/',
 
  186        'SendGrid' => 
'/[\d]{3} Ok: queued as (.*)/',
 
  187        'CampaignMonitor' => 
'/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
 
  196    protected $last_smtp_transaction_id;
 
  203    protected $smtp_conn;
 
  214        'smtp_code_ex' => 
'',
 
  253    protected function edebug($str, $level = 0)
 
  255        if ($level > $this->do_debug) {
 
  259        if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
 
  260            $this->Debugoutput->debug($str);
 
  265        if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, [
'error_log', 
'html', 
'echo'])) {
 
  266            call_user_func($this->Debugoutput, $str, $level);
 
  270        switch ($this->Debugoutput) {
 
  277                echo gmdate(
'Y-m-d H:i:s'), 
' ', htmlentities(
 
  278                    preg_replace(
'/[\r\n]+/', 
'', $str),
 
  286                $str = preg_replace(
'/\r\n|\r/m', 
"\n", $str);
 
  287                echo gmdate(
'Y-m-d H:i:s'),
 
  317        if (
null === $streamok) {
 
  318            $streamok = function_exists(
'stream_socket_client');
 
  325            $this->
setError(
'Already connected to a server');
 
  334            "Connection: opening to $host:$port, timeout=$timeout, options=" .
 
  336            self::DEBUG_CONNECTION
 
  341            $socket_context = stream_context_create(
$options);
 
  342            set_error_handler([$this, 
'errorHandler']);
 
  343            $this->smtp_conn = stream_socket_client(
 
  348                STREAM_CLIENT_CONNECT,
 
  351            restore_error_handler();
 
  355                'Connection: stream_socket_client not available, falling back to fsockopen',
 
  356                self::DEBUG_CONNECTION
 
  358            set_error_handler([$this, 
'errorHandler']);
 
  359            $this->smtp_conn = fsockopen(
 
  366            restore_error_handler();
 
  369        if (!is_resource($this->smtp_conn)) {
 
  371                'Failed to connect to server',
 
  377                'SMTP ERROR: ' . $this->
error[
'error']
 
  378                . 
": $errstr ($errno)",
 
  384        $this->
edebug(
'Connection: opened', self::DEBUG_CONNECTION);
 
  387        if (strpos(PHP_OS, 
'WIN') !== 0) {
 
  388            $max = (int) ini_get(
'max_execution_time');
 
  390            if (0 !== $max && $timeout > $max) {
 
  391                @set_time_limit($timeout);
 
  393            stream_set_timeout($this->smtp_conn, $timeout, 0);
 
  397        $this->
edebug(
'SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
 
  409        if (!$this->
sendCommand(
'STARTTLS', 
'STARTTLS', 220)) {
 
  414        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
 
  418        if (defined(
'STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
 
  419            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
 
  420            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
 
  424        set_error_handler([$this, 
'errorHandler']);
 
  425        $crypto_ok = stream_socket_enable_crypto(
 
  430        restore_error_handler();
 
  432        return (
bool) $crypto_ok;
 
  454        if (!$this->server_caps) {
 
  455            $this->
setError(
'Authentication is not allowed before HELO/EHLO');
 
  460        if (array_key_exists(
'EHLO', $this->server_caps)) {
 
  462            if (!array_key_exists(
'AUTH', $this->server_caps)) {
 
  463                $this->
setError(
'Authentication is not allowed at this stage');
 
  470            $this->
edebug(
'Auth method requested: ' . ($authtype ?: 
'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
 
  472                'Auth methods available on the server: ' . implode(
',', $this->server_caps[
'AUTH']),
 
  477            if (
null !== $authtype && !in_array($authtype, $this->server_caps[
'AUTH'], 
true)) {
 
  478                $this->
edebug(
'Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
 
  482            if (empty($authtype)) {
 
  485                foreach ([
'CRAM-MD5', 
'LOGIN', 
'PLAIN', 
'XOAUTH2'] as $method) {
 
  486                    if (in_array($method, $this->server_caps[
'AUTH'], 
true)) {
 
  491                if (empty($authtype)) {
 
  492                    $this->
setError(
'No supported authentication methods found');
 
  496                $this->
edebug(
'Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
 
  499            if (!in_array($authtype, $this->server_caps[
'AUTH'], 
true)) {
 
  500                $this->
setError(
"The requested authentication method \"$authtype\" is not supported by the server");
 
  504        } elseif (empty($authtype)) {
 
  510                if (!$this->
sendCommand(
'AUTH', 
'AUTH PLAIN', 334)) {
 
  516                    base64_encode(
"\0" . $username . 
"\0" . 
$password),
 
  525                if (!$this->
sendCommand(
'AUTH', 
'AUTH LOGIN', 334)) {
 
  528                if (!$this->
sendCommand(
'Username', base64_encode($username), 334)) {
 
  537                if (!$this->
sendCommand(
'AUTH CRAM-MD5', 
'AUTH CRAM-MD5', 334)) {
 
  541                $challenge = base64_decode(substr($this->last_reply, 4));
 
  550                if (
null === $OAuth) {
 
  553                $oauth = $OAuth->getOauth64();
 
  556                if (!$this->
sendCommand(
'AUTH', 
'AUTH XOAUTH2 ' . $oauth, 235)) {
 
  561                $this->
setError(
"Authentication method \"$authtype\" is not supported");
 
  581        if (function_exists(
'hash_hmac')) {
 
  594        if (strlen(
$key) > $bytelen) {
 
  597        $key = str_pad(
$key, $bytelen, chr(0x00));
 
  598        $ipad = str_pad(
'', $bytelen, chr(0x36));
 
  599        $opad = str_pad(
'', $bytelen, chr(0x5c));
 
  600        $k_ipad = 
$key ^ $ipad;
 
  601        $k_opad = 
$key ^ $opad;
 
  603        return md5($k_opad . pack(
'H*', md5($k_ipad . 
$data)));
 
  613        if (is_resource($this->smtp_conn)) {
 
  614            $sock_status = stream_get_meta_data($this->smtp_conn);
 
  615            if ($sock_status[
'eof']) {
 
  618                    'SMTP NOTICE: EOF caught while checking if connected',
 
  641        $this->server_caps = 
null;
 
  642        $this->helo_rply = 
null;
 
  643        if (is_resource($this->smtp_conn)) {
 
  645            fclose($this->smtp_conn);
 
  646            $this->smtp_conn = 
null; 
 
  647            $this->
edebug(
'Connection: closed', self::DEBUG_CONNECTION);
 
  664    public function data($msg_data)
 
  680        $lines = explode(
"\n", str_replace([
"\r\n", 
"\r"], 
"\n", $msg_data));
 
  687        $field = substr($lines[0], 0, strpos($lines[0], 
':'));
 
  689        if (!empty($field) && strpos($field, 
' ') === 
false) {
 
  693        foreach ($lines as $line) {
 
  695            if ($in_headers && $line === 
'') {
 
  700            while (isset($line[self::MAX_LINE_LENGTH])) {
 
  703                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), 
' ');
 
  707                    $pos = self::MAX_LINE_LENGTH - 1;
 
  708                    $lines_out[] = substr($line, 0, $pos);
 
  709                    $line = substr($line, $pos);
 
  712                    $lines_out[] = substr($line, 0, $pos);
 
  714                    $line = substr($line, $pos + 1);
 
  718                    $line = 
"\t" . $line;
 
  721            $lines_out[] = $line;
 
  724            foreach ($lines_out as $line_out) {
 
  726                if (!empty($line_out) && $line_out[0] === 
'.') {
 
  727                    $line_out = 
'.' . $line_out;
 
  729                $this->
client_send($line_out . static::LE, 
'DATA');
 
  736        $this->Timelimit *= 2;
 
  740        $this->Timelimit = $savetimelimit;
 
  775        $noerror = $this->
sendCommand($hello, $hello . 
' ' . $host, 250);
 
  780            $this->server_caps = 
null;
 
  794        $this->server_caps = [];
 
  795        $lines = explode(
"\n", $this->helo_rply);
 
  797        foreach ($lines as 
$n => 
$s) {
 
  799            $s = trim(substr(
$s, 4));
 
  803            $fields = explode(
' ', 
$s);
 
  804            if (!empty($fields)) {
 
  807                    $fields = $fields[0];
 
  809                    $name = array_shift($fields);
 
  812                            $fields = ($fields ? $fields[0] : 0);
 
  815                            if (!is_array($fields)) {
 
  823                $this->server_caps[
$name] = $fields;
 
  842        $useVerp = ($this->do_verp ? 
' XVERP' : 
'');
 
  846            'MAIL FROM:<' . 
$from . 
'>' . $useVerp,
 
  860    public function quit($close_on_error = 
true)
 
  862        $noerror = $this->
sendCommand(
'QUIT', 
'QUIT', 221);
 
  864        if ($noerror || $close_on_error) {
 
  887            $rcpt = 
'RCPT TO:<' . $address . 
'>';
 
  892            if (strpos(
$dsn, 
'NEVER') !== 
false) {
 
  895                foreach ([
'SUCCESS', 
'FAILURE', 
'DELAY'] as $value) {
 
  896                    if (strpos(
$dsn, $value) !== 
false) {
 
  902            $rcpt = 
'RCPT TO:<' . $address . 
'> NOTIFY=' . implode(
',', $notify);
 
  936            $this->
setError(
"Called $command without being connected");
 
  941        if ((strpos($commandstring, 
"\n") !== 
false) || (strpos($commandstring, 
"\r") !== 
false)) {
 
  942            $this->
setError(
"Command '$command' contained line breaks");
 
  946        $this->
client_send($commandstring . static::LE, $command);
 
  951        if (preg_match(
'/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
 
  952            $code = (int) $matches[1];
 
  953            $code_ex = (count($matches) > 2 ? $matches[2] : 
null);
 
  955            $detail = preg_replace(
 
  957                ($code_ex ? str_replace(
'.', 
'\\.', $code_ex) . 
' ' : 
'') . 
'/m',
 
  963            $code = (int) substr($this->last_reply, 0, 3);
 
  965            $detail = substr($this->last_reply, 4);
 
  968        $this->
edebug(
'SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
 
  970        if (!in_array(
$code, (array) $expect, 
true)) {
 
  972                "$command command failed",
 
  978                'SMTP ERROR: ' . $this->
error[
'error'] . 
': ' . $this->last_reply,
 
 1006        return $this->
sendCommand(
'SAML', 
"SAML FROM:$from", 250);
 
 1018        return $this->
sendCommand(
'VRFY', 
"VRFY $name", [250, 251]);
 
 1043        $this->
setError(
'The SMTP TURN command is not implemented');
 
 1044        $this->
edebug(
'SMTP NOTICE: ' . $this->
error[
'error'], self::DEBUG_CLIENT);
 
 1061        if (self::DEBUG_LOWLEVEL > $this->do_debug &&
 
 1062            in_array($command, [
'User & Password', 
'Username', 
'Password'], 
true)) {
 
 1063            $this->
edebug(
'CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
 
 1065            $this->
edebug(
'CLIENT -> SERVER: ' . 
$data, self::DEBUG_CLIENT);
 
 1067        set_error_handler([$this, 
'errorHandler']);
 
 1069        restore_error_handler();
 
 1081        return $this->error;
 
 1113        if (!$this->server_caps) {
 
 1114            $this->
setError(
'No HELO/EHLO was sent');
 
 1119        if (!array_key_exists(
$name, $this->server_caps)) {
 
 1120            if (
'HELO' === 
$name) {
 
 1121                return $this->server_caps[
'EHLO'];
 
 1123            if (
'EHLO' === 
$name || array_key_exists(
'EHLO', $this->server_caps)) {
 
 1126            $this->
setError(
'HELO handshake was used; No information about server extensions available');
 
 1131        return $this->server_caps[
$name];
 
 1156        if (!is_resource($this->smtp_conn)) {
 
 1161        stream_set_timeout($this->smtp_conn, $this->Timeout);
 
 1162        if ($this->Timelimit > 0) {
 
 1165        $selR = [$this->smtp_conn];
 
 1167        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
 
 1169            if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
 
 1171                    'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . 
' sec)',
 
 1172                    self::DEBUG_LOWLEVEL
 
 1177            $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
 
 1178            $this->
edebug(
'SMTP INBOUND: "' . trim($str) . 
'"', self::DEBUG_LOWLEVEL);
 
 1183            if (!isset($str[3]) || $str[3] === 
' ' || $str[3] === 
"\r" || $str[3] === 
"\n") {
 
 1187            $info = stream_get_meta_data($this->smtp_conn);
 
 1188            if (
$info[
'timed_out']) {
 
 1190                    'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . 
' sec)',
 
 1191                    self::DEBUG_LOWLEVEL
 
 1196            if ($endtime && time() > $endtime) {
 
 1198                    'SMTP -> get_lines(): timelimit reached (' .
 
 1199                    $this->Timelimit . 
' sec)',
 
 1200                    self::DEBUG_LOWLEVEL
 
 1216        $this->do_verp = $enabled;
 
 1241            'detail' => $detail,
 
 1242            'smtp_code' => $smtp_code,
 
 1243            'smtp_code_ex' => $smtp_code_ex,
 
 1254        $this->Debugoutput = $method;
 
 1274        $this->do_debug = $level;
 
 1294        $this->Timeout = $timeout;
 
 1315    protected function errorHandler($errno, $errmsg, $errfile = 
'', $errline = 0)
 
 1317        $notice = 
'Connection failed.';
 
 1324            "$notice Error #$errno: $errmsg [$errfile line $errline]",
 
 1325            self::DEBUG_CONNECTION
 
 1342        if (empty($reply)) {
 
 1343            $this->last_smtp_transaction_id = 
null;
 
 1345            $this->last_smtp_transaction_id = 
false;
 
 1346            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
 
 1348                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
 
 1349                    $this->last_smtp_transaction_id = trim($matches[1]);
 
 1355        return $this->last_smtp_transaction_id;
 
 1369        return $this->last_smtp_transaction_id;
 
An exception for terminatinating execution or to throw for unit testing.
PHPMailer RFC821 SMTP email transport class.
getTimeout()
Get SMTP timeout.
parseHelloFields($type)
Parse a reply to HELO/EHLO command to discover server extensions.
getVerp()
Get VERP address generation mode.
setDebugLevel($level=0)
Set debug output level.
quit($close_on_error=true)
Send an SMTP QUIT command.
getServerExt($name)
Get metadata about the SMTP server from its HELO/EHLO response.
noop()
Send an SMTP NOOP command.
$smtp_transaction_id_patterns
setDebugOutput($method='echo')
Set debug output method.
recipient($address, $dsn='')
Send an SMTP RCPT command.
edebug($str, $level=0)
Output debugging info via a user-selected method.
getServerExtList()
Get SMTP extensions available on the server.
data($msg_data)
Send an SMTP DATA command.
sendCommand($command, $commandstring, $expect)
Send a command to an SMTP server and check its return code.
setTimeout($timeout=0)
Set SMTP timeout.
getError()
Get the latest error.
setVerp($enabled=false)
Enable or disable VERP address generation.
client_send($data, $command='')
Send raw data to the server.
connect($host, $port=null, $timeout=30, $options=[])
Connect to an SMTP server.
authenticate( $username, $password, $authtype=null, $OAuth=null)
Perform SMTP authentication.
mail($from)
Send an SMTP MAIL command.
hello($host='')
Send an SMTP HELO or EHLO command.
recordLastTransactionID()
Extract and return the ID of the last SMTP transaction based on a list of patterns provided in SMTP::...
getDebugOutput()
Get debug output method.
verify($name)
Send an SMTP VRFY command.
getDebugLevel()
Get debug output level.
startTLS()
Initiate a TLS (encrypted) session.
getLastTransactionID()
Get the queue/transaction ID of the last SMTP transaction If no reply has been received yet,...
errorHandler($errno, $errmsg, $errfile='', $errline=0)
Reports an error number and string.
reset()
Send an SMTP RSET command.
connected()
Check connection state.
hmac($data, $key)
Calculate an MD5 HMAC hash.
sendAndMail($from)
Send an SMTP SAML command.
turn()
Send an SMTP TURN command.
setError($message, $detail='', $smtp_code='', $smtp_code_ex='')
Set error messages and codes.
get_lines()
Read the SMTP server's response.
sendHello($hello, $host)
Send an SMTP HELO or EHLO command.
getLastReply()
Get the last reply from the server.
close()
Close the socket and clean up the state of the class.
error($a_errmsg)
set error message @access public
catch(Exception $e) $message
foreach($paths as $path) $dsn
Get an OAuth2 token from an OAuth2 provider.