Skip to content

Instantly share code, notes, and snippets.

@opentechnologist
Created February 4, 2026 07:17
Show Gist options
  • Select an option

  • Save opentechnologist/7b490e8ffd843fcb83aad54a170753b5 to your computer and use it in GitHub Desktop.

Select an option

Save opentechnologist/7b490e8ffd843fcb83aad54a170753b5 to your computer and use it in GitHub Desktop.
basic smtp class in php for sending simple email.
<?php
/**
* SimpleSMTPMailer Class
* Author: Mario Flores Rey II <mr3y2@yahoo.com>
*
* Minimal, pure-PHP SMTP client for sending emails over SMTPS (implicit SSL on port 465).
* Compatible with PHP 5.6.40+. Used for testing purposes only. Avoid production use.
*
* Usage:
* $mailer = new SimpleSMTPMailer([
* 'host' => 'smtp.example.com',
* 'port' => 465,
* 'username' => 'username@example.com',
* 'password' => 'your_password_or_app_password',
* 'from' => 'from@example.com',
* 'from_name' => 'Sender Name',
* 'verify_peer' => false,
* 'timeout' => 30,
* ]);
* $mailer->sendMail('recipient@example.com', 'Recipient Name', 'Subject', 'Message body');
*
* Limitation Notes:
* - Implements just enough of the SMTP protocol to authenticate with AUTH LOGIN to send
* single simple email message.
* - For testing, disable certificate verification (not recommended for production).
* - Only use US ASCII characters. Do not use accented foreign or fancy utf8 characters.
* - Most providers (Gmail, Office365, etc.) will prefer app passwords or OAuth2 instead.
*/
class SimpleSMTPMailer
{
const CRLF = "\r\n";
private $config;
private $socket;
/**
* Constructor
*
* @param array $config Configuration array with SMTP settings
*/
public function __construct(array $config)
{
$this->config = (object) array_merge([
'host' => null,
'port' => 465,
'username' => null,
'password' => null,
'from' => null,
'from_name' => null,
'verify_peer' => false, // For testing only, must override!
'cafile' => null, // For testing only, must override!
'timeout' => 30,
'local_domain' => 'localhost',
], $config);
}
/**
* Send an email
*
* @param string $to Recipient email address
* @param string $to_name Recipient name
* @param string $subject Email subject
* @param string $body Email body
* @return bool True on success, false on failure
*/
public function sendMail($to, $to_name, $subject, $body)
{
try {
$this->connect();
$this->authenticate();
$this->sendMessage($to, $to_name, $subject, $body);
$this->disconnect();
printf('Message sent successfully.%s', PHP_EOL);
return true;
} catch (Exception $error) {
printf('Error: %s%s', $error->getMessage(), PHP_EOL);
if ($this->socket) {
fclose($this->socket);
}
return false;
}
}
/**
* Connect to SMTP server
*
* @throws Exception on connection failure
*/
private function connect()
{
if ($this->socket) {
throw new Exception('Already connected.');
}
$context_options = [
'ssl' => [
'verify_peer' => (bool) $this->config->verify_peer,
'verify_peer_name' => (bool) $this->config->verify_peer,
],
];
if (!empty($this->config->cafile)) {
$context_options['ssl']['cafile'] = $this->config->cafile;
}
$stream_context = stream_context_create($context_options);
$remote_address = 'ssl://' . $this->config->host . ':' . $this->config->port;
$error_number = 0;
$error_string = '';
$this->socket = @stream_socket_client(
$remote_address,
$error_number,
$error_string,
$this->config->timeout,
STREAM_CLIENT_CONNECT,
$stream_context
);
if (!$this->socket) {
throw new Exception(sprintf(
'Failed to connect to %s: (%d) %s',
$remote_address,
$error_number,
$error_string
));
}
stream_set_timeout($this->socket, $this->config->timeout);
// Read server banner
$server_banner = $this->receiveResponse();
printf('S: %s', $server_banner);
// Send EHLO
$response = $this->sendCommand('EHLO ' . $this->config->local_domain);
printf('S: %s', $response);
}
/**
* Authenticate with SMTP server using AUTH LOGIN
*
* @throws Exception on authentication failure
*/
private function authenticate()
{
$response = $this->sendCommand('AUTH LOGIN');
printf('S: %s', $response);
if (strpos($response, '334') !== 0) {
throw new Exception('Server did not accept AUTH LOGIN. Response: ' . $response);
}
$response = $this->sendCommand(base64_encode($this->config->username));
printf('S: %s', $response);
$response = $this->sendCommand(base64_encode($this->config->password));
printf('S: %s', $response);
if (strpos($response, '235') !== 0) {
throw new Exception('Authentication failed. Response: ' . $response);
}
}
/**
* Send the actual email message
*
* @param string $to Recipient email
* @param string $to_name Recipient name
* @param string $subject Email subject
* @param string $body Email body
* @throws Exception on send failure
*/
private function sendMessage($to, $to_name, $subject, $body)
{
// MAIL FROM
$response = $this->sendCommand('MAIL FROM:<' . $this->config->from . '>');
printf('S: %s', $response);
if (strpos($response, '250') !== 0) {
throw new Exception('MAIL FROM command failed. Response: ' . $response);
}
// RCPT TO
$response = $this->sendCommand('RCPT TO:<' . $to . '>');
printf('S: %s', $response);
if (strpos($response, '250') !== 0 && strpos($response, '251') !== 0) {
throw new Exception('RCPT TO command failed. Response: ' . $response);
}
// DATA
$response = $this->sendCommand('DATA');
printf('S: %s', $response);
if (strpos($response, '354') !== 0) {
throw new Exception('Server did not accept DATA command. Response: ' . $response);
}
// Build and send email message
$email_content = $this->buildEmailMessage($to, $to_name, $subject, $body);
$response = $this->sendSocket($email_content . self::CRLF . '.' . self::CRLF);
printf('S: %s', $response);
if (strpos($response, '250') !== 0) {
throw new Exception('Message was not accepted. Response: ' . $response);
}
}
/**
* Build the email message with headers and body
*
* @param string $to Recipient email
* @param string $to_name Recipient name
* @param string $subject Email subject
* @param string $body Email message content
* @return string Formatted email message
*/
private function buildEmailMessage($to, $to_name, $subject, $body)
{
$date = date('r'); // RFC 2822 formatted date
$email_content = implode(self::CRLF, [
"From: {$this->config->from_name} <{$this->config->from}>",
"To: {$to_name} <{$to}>",
"Subject: {$subject}",
"Date: {$date}",
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
self::CRLF, // Empty line to separate headers from body
$body
]);
// Escape lines starting with a dot by prepending another dot
$email_content = preg_replace('/^\./m', '..', $email_content);
return $email_content;
}
/**
* Disconnect from SMTP server
*/
private function disconnect()
{
if ($this->socket) {
$response = $this->sendCommand('QUIT');
printf('S: %s', $response);
fclose($this->socket);
$this->socket = null;
}
}
/**
* Receive response from SMTP server
*
* @return string Server response
*/
private function receiveResponse()
{
$response = '';
// Read lines until line begins with 3-digit code + space
while ($line = fgets($this->socket, 515)) {
$response .= $line;
// Check if this is the last line of a multi-line response:
// e.g. "250-something\r\n" continues, "250 something\r\n" ends
if (preg_match('/^[0-9]{3} /', $line)) {
break;
}
}
return $response;
}
/**
* Send data to socket and receive response
*
* @param string $string Data to send
* @return string Server response
*/
private function sendSocket($string)
{
fwrite($this->socket, $string);
return $this->receiveResponse();
}
/**
* Send SMTP command and receive response
*
* @param string $command SMTP command
* @return string Server response
*/
private function sendCommand($command)
{
return $this->sendSocket($command . self::CRLF);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment