-
Notifications
You must be signed in to change notification settings - Fork 0
Recipe SMTP Client
A minimal raw SMTP client that opens an implicit-TLS connection to Gmail's port 465 and performs the initial EHLO exchange. Useful as a smoke test for the TLS client and to learn the protocol; do not ship this as a real mailer — use initphp/mailer or another battle-tested library for that.
smtp-probe.php:
<?php
require __DIR__ . '/vendor/autoload.php';
use InitPHP\Socket\Socket;
use InitPHP\Socket\Enum\Transport;
$client = Socket::client(Transport::SSL, 'smtp.gmail.com', 465, timeout: 10.0)
->option('verify_peer', false)
->option('verify_peer_name', false);
$client->connect();
echo $client->read(1024); // 220 banner
$client->write("EHLO localhost\r\n");
echo $client->read(1024); // 250 capabilities
$client->write("QUIT\r\n");
echo $client->read(1024); // 221 closing
$client->disconnect();php smtp-probe.phpExpected output (truncated):
220 smtp.gmail.com ESMTP …
250-smtp.gmail.com at your service, …
250-SIZE …
250-STARTTLS
250 SMTPUTF8
221 2.0.0 closing connection
-
Transport::SSL— Gmail's port 465 serves implicit TLS; the connection is encrypted from the first byte. For port 587 you would start withTransport::TCPand upgrade in place withcrypto()afterSTARTTLS. -
timeout: 10.0— the connect (and TLS handshake) must finish inside this budget. -
verify_peeris set tofalsehere to keep the snippet self-contained. In production pointcafileat the system trust store (/etc/ssl/certs/ca-certificates.crton Debian/Ubuntu,/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pemon RHEL/Alma) and leaveverify_peeron.
SMTP replies are line-oriented and may span multiple lines for one logical response (250-foo continuation, 250 bar final). Looping until the final-line marker shows up makes the read robust:
function smtpRead(\InitPHP\Socket\Interfaces\SocketClientInterface $client): string
{
$out = '';
while (($chunk = $client->read(1024)) !== null) {
$out .= $chunk;
// RFC 5321: final line of a multi-line response uses "<code><space>".
if (preg_match('/^\d{3} [^\r\n]*\r?\n$/m', $chunk) === 1) {
break;
}
}
return $out;
}Use it in place of the raw $client->read(1024) calls in the script above.
For SMTP submission over STARTTLS, the protocol starts unencrypted and upgrades:
use InitPHP\Socket\Enum\{Transport, CryptoMethod};
$client = Socket::client(Transport::TCP, 'smtp.example.com', 587, /* no TLS yet */);
$client->connect();
// 220 banner...
echo $client->read(1024);
$client->write("EHLO localhost\r\n");
echo $client->read(1024);
$client->write("STARTTLS\r\n");
echo $client->read(1024);
// 220 Ready to start TLSThe package's
Client\TCPdoes not expose a TLS upgrade —crypto()lives on the stream-backed clients (TLS/SSL). For a true STARTTLS flow you would call PHP'sstream_socket_enable_crypto($client->getSocket(), true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT)directly on the underlying socket. Or open withTransport::TLSand usecrypto()once connected if your peer accepts the deferred handshake.
For production mail handling, use a mail library that knows about AUTH, MIME, headers and retries. The example above is a probe — it just shows that the TLS client connects and reads SMTP greetings. See initphp/mailer for the full client.
- Transport TLS / Transport SSL — reference for the stream-backed clients.
-
SSL Context Options — every key
option()accepts. - Recipe Self Signed TLS — local-loopback equivalent for testing.
initphp/socket · MIT · PHP 8.1+ · part of the InitPHP family · file issues at InitPHP/Socket/issues
Getting started
Transports
Concepts
Reference
Recipes
Operational