Created
February 4, 2026 07:17
-
-
Save opentechnologist/7b490e8ffd843fcb83aad54a170753b5 to your computer and use it in GitHub Desktop.
basic smtp class in php for sending simple email.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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