Skip to content

Commit 4b3f312

Browse files
committed
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
1 parent dc43428 commit 4b3f312

File tree

5 files changed

+186
-7
lines changed

5 files changed

+186
-7
lines changed

app/Web/Mail.php

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
namespace App\Web;
55

66
use InvalidArgumentException;
7+
use PHPMailer\PHPMailer\PHPMailer;
8+
use PHPMailer\PHPMailer\Exception as PHPMailerException;
79

810
class Mail
911
{
@@ -60,7 +62,7 @@ public function to(string $mail)
6062
if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
6163
throw new InvalidArgumentException('Mail not valid.');
6264
}
63-
$this->to = "<$mail>";
65+
$this->to = $mail;
6466
return $this;
6567
}
6668

@@ -138,15 +140,89 @@ public function send()
138140
throw new InvalidArgumentException('Message cannot be null.');
139141
}
140142

141-
$this->setHeaders();
143+
if (self::$testing) {
144+
return 1;
145+
}
142146

143-
$this->headers .= $this->additionalHeaders;
144-
$message = html_entity_decode($this->message);
147+
$config = resolve('config');
148+
$mailConfig = $config['mail'] ?? [];
149+
$driver = $mailConfig['driver'] ?? 'mail';
145150

146-
if (self::$testing) {
151+
if ($driver === 'smtp') {
152+
return $this->sendViaSMTP($mailConfig);
153+
}
154+
155+
return $this->sendViaMail();
156+
}
157+
158+
/**
159+
* Send email using SMTP via PHPMailer
160+
*
161+
* @param array $config
162+
* @return int
163+
*/
164+
protected function sendViaSMTP(array $config)
165+
{
166+
$mail = new PHPMailer(true);
167+
168+
try {
169+
// Server settings
170+
$mail->isSMTP();
171+
$mail->Host = $config['host'] ?? '';
172+
$mail->Port = $config['port'] ?? 587;
173+
174+
// Authentication
175+
if (!empty($config['username'])) {
176+
$mail->SMTPAuth = true;
177+
$mail->Username = $config['username'];
178+
$mail->Password = $config['password'] ?? '';
179+
}
180+
181+
// Encryption
182+
$encryption = $config['encryption'] ?? 'tls';
183+
if ($encryption === 'tls') {
184+
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
185+
} elseif ($encryption === 'ssl') {
186+
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
187+
} else {
188+
$mail->SMTPSecure = '';
189+
$mail->SMTPAutoTLS = false;
190+
}
191+
192+
// Sender
193+
$fromMail = !empty($config['from']) ? $config['from'] : $this->fromMail;
194+
$fromName = !empty($config['from_name']) ? $config['from_name'] : ($this->fromName ?? '');
195+
$mail->setFrom($fromMail, $fromName);
196+
197+
// Recipient
198+
$mail->addAddress($this->to);
199+
200+
// Content
201+
$mail->isHTML(true);
202+
$mail->CharSet = 'UTF-8';
203+
$mail->Subject = $this->subject;
204+
$mail->Body = '<html><body>'.html_entity_decode($this->message).'</body></html>';
205+
$mail->AltBody = strip_tags(html_entity_decode($this->message));
206+
207+
$mail->send();
147208
return 1;
209+
} catch (PHPMailerException $e) {
210+
error_log('Mail sending failed: '.$mail->ErrorInfo);
211+
return 0;
148212
}
213+
}
214+
215+
/**
216+
* Send email using PHP's mail() function (legacy method)
217+
*
218+
* @return int
219+
*/
220+
protected function sendViaMail()
221+
{
222+
$this->setHeaders();
223+
$this->headers .= $this->additionalHeaders;
224+
$message = html_entity_decode($this->message);
149225

150-
return (int) mail($this->to, $this->subject, "<html><body>$message</body></html>", $this->headers);
226+
return (int) mail("<{$this->to}>", $this->subject, "<html><body>$message</body></html>", $this->headers);
151227
}
152228
}

bootstrap/app.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@
4848
'base_domain' => null,
4949
'user_domain' => null,
5050
],
51+
'mail' => [
52+
'driver' => 'mail',
53+
'host' => '',
54+
'port' => 587,
55+
'encryption' => 'tls',
56+
'username' => '',
57+
'password' => '',
58+
'from' => '',
59+
'from_name' => '',
60+
],
5161
], require CONFIG_FILE);
5262

5363
$builder = new ContainerBuilder();

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"league/flysystem-cached-adapter": "^1.1",
2020
"maennchen/zipstream-php": "^2.0",
2121
"monolog/monolog": "^1.23",
22+
"phpmailer/phpmailer": "^6.8",
2223
"php-di/slim-bridge": "^3.0",
2324
"sapphirecat/slim4-http-interop-adapter": "^1.0",
2425
"slim/psr7": "^1.5",

composer.lock

Lines changed: 82 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config.example.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,15 @@
1212
'driver' => 'local',
1313
'path' => realpath(__DIR__).'/storage',
1414
],
15+
// SMTP configuration (optional - if not configured, PHP's mail() function will be used)
16+
// 'mail' => [
17+
// 'driver' => 'smtp', // 'smtp' or 'mail' (default: 'mail')
18+
// 'host' => 'smtp.example.com',
19+
// 'port' => 587,
20+
// 'encryption' => 'tls', // 'tls', 'ssl', or '' for none
21+
// 'username' => 'your-username',
22+
// 'password' => 'your-password',
23+
// 'from' => 'noreply@example.com',
24+
// 'from_name' => 'XBackBone',
25+
// ],
1526
];

0 commit comments

Comments
 (0)