126 lines
4.4 KiB
PHP
126 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Email;
|
|
|
|
use PHPMailer\PHPMailer\Exception as MailerException;
|
|
use PHPMailer\PHPMailer\PHPMailer;
|
|
|
|
class EmailDispatchService
|
|
{
|
|
public function __construct(private EmailProfileService $profiles)
|
|
{
|
|
}
|
|
|
|
public function send(
|
|
string $recipient,
|
|
string $subject,
|
|
string $htmlMessage,
|
|
?string $profile = null,
|
|
?string $replyToEmail = null,
|
|
?string $replyToName = null,
|
|
array $attachments = []
|
|
): bool {
|
|
$profile = $this->profiles->resolveProfile($profile);
|
|
$cfg = $this->profiles->getProfileConfig($profile);
|
|
|
|
if (empty($cfg['host']) || empty($cfg['user']) || $cfg['pass'] === '') {
|
|
logger()->error("[mail:$profile] Missing SMTP config (host/user/pass). Check MAIL_{$this->profiles->envKeyFromProfile($profile)}_* or MAIL_DEFAULT_*/MAIL_*.");
|
|
return false;
|
|
}
|
|
|
|
$debugEnabled = (bool) env('MAIL_DEBUG', false);
|
|
$timeout = (int) env('MAIL_TIMEOUT', 15);
|
|
$keepAlive = (bool) env('MAIL_KEEPALIVE', false);
|
|
$verifyPeer = filter_var(env('MAIL_VERIFY_PEER', 'true'), FILTER_VALIDATE_BOOLEAN);
|
|
|
|
$mail = new PHPMailer(true);
|
|
|
|
try {
|
|
if ($debugEnabled) {
|
|
ob_start();
|
|
}
|
|
|
|
$mail->isSMTP();
|
|
$mail->Host = $cfg['host'];
|
|
$mail->Port = $cfg['port'];
|
|
$mail->SMTPAuth = true;
|
|
$mail->Username = $cfg['user'];
|
|
$mail->Password = $cfg['pass'];
|
|
$mail->CharSet = 'UTF-8';
|
|
$mail->Timeout = $timeout;
|
|
$mail->SMTPKeepAlive = $keepAlive;
|
|
$mail->SMTPAutoTLS = true;
|
|
|
|
if ($cfg['encryption'] === 'ssl') {
|
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
|
} else {
|
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
|
}
|
|
|
|
$mail->SMTPOptions = [
|
|
'ssl' => [
|
|
'verify_peer' => $verifyPeer,
|
|
'verify_peer_name' => $verifyPeer,
|
|
'allow_self_signed' => !$verifyPeer,
|
|
],
|
|
];
|
|
|
|
$mail->SMTPDebug = $debugEnabled ? 3 : 0;
|
|
$mail->Debugoutput = 'error_log';
|
|
|
|
$mail->setFrom($cfg['fromEmail'], $cfg['fromName']);
|
|
if (!empty($cfg['returnPath'])) {
|
|
$mail->Sender = $cfg['returnPath'];
|
|
}
|
|
|
|
$mail->clearReplyTos();
|
|
$rtEmail = env('MAIL_DEFAULT_REPLY_TO');
|
|
$rtName = env('MAIL_DEFAULT_REPLY_TO_NAME');
|
|
if (!$rtEmail || !filter_var($rtEmail, FILTER_VALIDATE_EMAIL)) {
|
|
$rtEmail = $replyToEmail ?: ($cfg['replyTo'] ?: $cfg['fromEmail']);
|
|
}
|
|
if (!$rtName) {
|
|
$rtName = $replyToName ?: ($cfg['replyToName'] ?: $cfg['fromName']);
|
|
}
|
|
$rtName = $this->profiles->sanitizeReplyToName($rtName, $cfg['fromName']);
|
|
if ($rtEmail) {
|
|
$mail->addReplyTo($rtEmail, $rtName);
|
|
}
|
|
|
|
if (!empty($cfg['dkim']['domain']) && !empty($cfg['dkim']['private']) && !empty($cfg['dkim']['selector'])) {
|
|
$mail->DKIM_domain = $cfg['dkim']['domain'];
|
|
$mail->DKIM_private = $cfg['dkim']['private'];
|
|
$mail->DKIM_selector = $cfg['dkim']['selector'];
|
|
$mail->DKIM_identity = $cfg['fromEmail'];
|
|
}
|
|
|
|
$mail->addAddress($recipient);
|
|
|
|
foreach ($attachments as $att) {
|
|
if (!empty($att['path']) && is_file($att['path'])) {
|
|
$mail->addAttachment($att['path'], $att['name'] ?? '');
|
|
}
|
|
}
|
|
|
|
$mail->isHTML(true);
|
|
$mail->Subject = $subject;
|
|
$mail->Body = $htmlMessage;
|
|
|
|
$ok = $mail->send();
|
|
$dbg = $debugEnabled ? (ob_get_clean() ?: '') : '';
|
|
|
|
if ($ok) {
|
|
logger()->info("[mail:$profile] Sent to {$recipient}, subj='{$subject}' via {$cfg['host']}:{$cfg['port']}/{$cfg['encryption']}");
|
|
return true;
|
|
}
|
|
|
|
logger()->error("[mail:$profile] Failed: {$mail->ErrorInfo}. Debug: {$dbg}");
|
|
return false;
|
|
} catch (MailerException $e) {
|
|
$dbg = $debugEnabled ? (ob_get_clean() ?: '') : '';
|
|
logger()->error("[mail:$profile] Exception: {$e->getMessage()} | PHPMailer: {$mail->ErrorInfo} | Debug: {$dbg}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|