From 4b3f312aa7faa8d6fd1ac9f2e041ec2b8ec69c6f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Feb 2026 00:53:19 +0000 Subject: [PATCH] feat: add SMTP email support for password recovery and notifications Add PHPMailer library to enable SMTP email sending. The Mail class now supports both PHP's native mail() function and SMTP via PHPMailer. Configuration can be set in config.php with host, port, encryption, and authentication settings. https://claude.ai/code/session_01SQh9ehdKVuqJcMnj8NKgJK --- app/Web/Mail.php | 88 ++++++++++++++++++++++++++++++++++++++++++---- bootstrap/app.php | 10 ++++++ composer.json | 1 + composer.lock | 83 ++++++++++++++++++++++++++++++++++++++++++- config.example.php | 11 ++++++ 5 files changed, 186 insertions(+), 7 deletions(-) diff --git a/app/Web/Mail.php b/app/Web/Mail.php index df7f1173..b7ac81e2 100644 --- a/app/Web/Mail.php +++ b/app/Web/Mail.php @@ -4,6 +4,8 @@ namespace App\Web; use InvalidArgumentException; +use PHPMailer\PHPMailer\PHPMailer; +use PHPMailer\PHPMailer\Exception as PHPMailerException; class Mail { @@ -60,7 +62,7 @@ public function to(string $mail) if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Mail not valid.'); } - $this->to = "<$mail>"; + $this->to = $mail; return $this; } @@ -138,15 +140,89 @@ public function send() throw new InvalidArgumentException('Message cannot be null.'); } - $this->setHeaders(); + if (self::$testing) { + return 1; + } - $this->headers .= $this->additionalHeaders; - $message = html_entity_decode($this->message); + $config = resolve('config'); + $mailConfig = $config['mail'] ?? []; + $driver = $mailConfig['driver'] ?? 'mail'; - if (self::$testing) { + if ($driver === 'smtp') { + return $this->sendViaSMTP($mailConfig); + } + + return $this->sendViaMail(); + } + + /** + * Send email using SMTP via PHPMailer + * + * @param array $config + * @return int + */ + protected function sendViaSMTP(array $config) + { + $mail = new PHPMailer(true); + + try { + // Server settings + $mail->isSMTP(); + $mail->Host = $config['host'] ?? ''; + $mail->Port = $config['port'] ?? 587; + + // Authentication + if (!empty($config['username'])) { + $mail->SMTPAuth = true; + $mail->Username = $config['username']; + $mail->Password = $config['password'] ?? ''; + } + + // Encryption + $encryption = $config['encryption'] ?? 'tls'; + if ($encryption === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } elseif ($encryption === 'ssl') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } else { + $mail->SMTPSecure = ''; + $mail->SMTPAutoTLS = false; + } + + // Sender + $fromMail = !empty($config['from']) ? $config['from'] : $this->fromMail; + $fromName = !empty($config['from_name']) ? $config['from_name'] : ($this->fromName ?? ''); + $mail->setFrom($fromMail, $fromName); + + // Recipient + $mail->addAddress($this->to); + + // Content + $mail->isHTML(true); + $mail->CharSet = 'UTF-8'; + $mail->Subject = $this->subject; + $mail->Body = ''.html_entity_decode($this->message).''; + $mail->AltBody = strip_tags(html_entity_decode($this->message)); + + $mail->send(); return 1; + } catch (PHPMailerException $e) { + error_log('Mail sending failed: '.$mail->ErrorInfo); + return 0; } + } + + /** + * Send email using PHP's mail() function (legacy method) + * + * @return int + */ + protected function sendViaMail() + { + $this->setHeaders(); + $this->headers .= $this->additionalHeaders; + $message = html_entity_decode($this->message); - return (int) mail($this->to, $this->subject, "$message", $this->headers); + return (int) mail("<{$this->to}>", $this->subject, "$message", $this->headers); } } diff --git a/bootstrap/app.php b/bootstrap/app.php index d8299982..383d4855 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -48,6 +48,16 @@ 'base_domain' => null, 'user_domain' => null, ], + 'mail' => [ + 'driver' => 'mail', + 'host' => '', + 'port' => 587, + 'encryption' => 'tls', + 'username' => '', + 'password' => '', + 'from' => '', + 'from_name' => '', + ], ], require CONFIG_FILE); $builder = new ContainerBuilder(); diff --git a/composer.json b/composer.json index 5e48b739..93765354 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "league/flysystem-cached-adapter": "^1.1", "maennchen/zipstream-php": "^2.0", "monolog/monolog": "^1.23", + "phpmailer/phpmailer": "^6.8", "php-di/slim-bridge": "^3.0", "sapphirecat/slim4-http-interop-adapter": "^1.0", "slim/psr7": "^1.5", diff --git a/composer.lock b/composer.lock index d7c2489d..c52d05ad 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "97bc49fc42c453ead436b0ff2a160758", + "content-hash": "3f788bbbff668583e19bc1a858a3da3a", "packages": [ { "name": "aws/aws-crt-php", @@ -1966,6 +1966,87 @@ }, "time": "2024-06-19T15:47:45+00:00" }, + { + "name": "phpmailer/phpmailer", + "version": "v6.12.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2025-10-15T16:49:08+00:00" + }, { "name": "psr/cache", "version": "1.0.1", diff --git a/config.example.php b/config.example.php index f79cbe4c..df9e4370 100644 --- a/config.example.php +++ b/config.example.php @@ -12,4 +12,15 @@ 'driver' => 'local', 'path' => realpath(__DIR__).'/storage', ], + // SMTP configuration (optional - if not configured, PHP's mail() function will be used) + // 'mail' => [ + // 'driver' => 'smtp', // 'smtp' or 'mail' (default: 'mail') + // 'host' => 'smtp.example.com', + // 'port' => 587, + // 'encryption' => 'tls', // 'tls', 'ssl', or '' for none + // 'username' => 'your-username', + // 'password' => 'your-password', + // 'from' => 'noreply@example.com', + // 'from_name' => 'XBackBone', + // ], ];