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', + // ], ];