Teren-app/app/Services/EmailSender.php
2025-10-12 00:20:03 +02:00

193 lines
7.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\EmailLog;
use App\Models\MailProfile;
use Symfony\Component\Mailer\Mailer as SymfonyMailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
class EmailSender
{
public function __construct(public EmailTemplateRenderer $renderer) {}
/**
* Build and send the message described by the EmailLog. Returns ['message_id' => string|null].
* Throws on transport errors so the Job can retry.
*/
public function sendFromLog(EmailLog $log): array
{
// Resolve sending profile
$profile = null;
if ($log->mail_profile_id) {
$profile = MailProfile::query()->find($log->mail_profile_id);
}
if (! $profile) {
$profile = MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first();
}
$embed = $log->embed_mode ?: 'base64';
$subject = (string) $log->subject;
$html = (string) optional($log->body)->body_html ?? '';
$text = (string) optional($log->body)->body_text ?? '';
// Inline CSS and handle images similarly to controller
if ($html !== '') {
if ($embed === 'base64') {
try {
$imageInliner = app(\App\Services\EmailImageInliner::class);
$html = $imageInliner->inline($html);
} catch (\Throwable $e) {
}
} else {
// Best effort absolutize /storage URLs using app.url
$base = (string) (config('app.asset_url') ?: config('app.url'));
$host = $base !== '' ? rtrim($base, '/') : null;
if ($host) {
$html = preg_replace_callback('#<img([^>]+)src=["\']([^"\']+)["\']([^>]*)>#i', function (array $m) use ($host) {
$before = $m[1] ?? '';
$src = $m[2] ?? '';
$after = $m[3] ?? '';
$path = $src;
if (preg_match('#^https?://#i', $src)) {
$parts = parse_url($src);
$path = $parts['path'] ?? '';
if (! preg_match('#^/?storage/#i', (string) $path)) {
return $m[0];
}
} else {
if (! preg_match('#^/?storage/#i', (string) $path)) {
return $m[0];
}
}
$rel = '/'.ltrim(preg_replace('#^/?storage/#i', 'storage/', (string) $path), '/');
$abs = rtrim($host, '/').$rel;
return '<img'.$before.'src="'.$abs.'"'.$after.'>';
}, $html);
}
}
try {
$inliner = new CssToInlineStyles;
$html = $inliner->convert($html);
} catch (\Throwable $e) {
}
}
// Transport setup (Symfony Mailer preferred when profile exists)
$messageId = null;
if ($profile) {
$host = $profile->host;
$port = (int) ($profile->port ?: 587);
$encryption = $profile->encryption ?: 'tls';
$username = $profile->username ?: '';
$password = (string) ($profile->decryptPassword() ?? '');
$scheme = $encryption === 'ssl' ? 'smtps' : 'smtp';
$query = $encryption === 'tls' ? '?encryption=tls' : '';
$dsn = sprintf('%s://%s:%s@%s:%d%s', $scheme, rawurlencode($username), rawurlencode($password), $host, $port, $query);
$transport = Transport::fromDsn($dsn);
$mailer = new SymfonyMailer($transport);
$fromAddr = (string) ($log->from_email ?: ($profile->from_address ?: ($username ?: (config('mail.from.address') ?? ''))));
$fromName = (string) ($log->from_name ?: ($profile->from_name ?: (config('mail.from.name') ?? config('app.name') ?? '')));
// Build email with safe Address instances (Symfony Address does not allow null name)
$fromAddress = $fromName !== ''
? new Address($fromAddr ?: $log->to_email, $fromName)
: new Address($fromAddr ?: $log->to_email);
$toAddress = (string) ($log->to_name ?? '') !== ''
? new Address($log->to_email, (string) $log->to_name)
: new Address($log->to_email);
$email = (new Email)
->from($fromAddress)
->subject($subject);
// If multiple recipients are present, address to all; otherwise single to
$toList = (array) ($log->to_recipients ?? []);
if (! empty($toList)) {
$addresses = [];
foreach ($toList as $addr) {
$addr = trim((string) $addr);
if ($addr !== '' && filter_var($addr, FILTER_VALIDATE_EMAIL)) {
$addresses[] = new Address($addr);
}
}
if (! empty($addresses)) {
$email->to(...$addresses);
} else {
$email->to($toAddress);
}
} else {
$email->to($toAddress);
}
if (! empty($text)) {
$email->text($text);
}
if (! empty($html)) {
$email->html($html);
}
if (! empty($log->reply_to)) {
$email->replyTo($log->reply_to);
}
$mailer->send($email);
$headers = $email->getHeaders();
$messageIdHeader = $headers->get('Message-ID');
$messageId = $messageIdHeader ? $messageIdHeader->getBodyAsString() : null;
} else {
// Fallback to Laravel mailer
if (! empty($html)) {
\Mail::html($html, function ($message) use ($log, $subject, $text) {
$toList = (array) ($log->to_recipients ?? []);
if (! empty($toList)) {
$message->to($toList);
} else {
$toName = (string) ($log->to_name ?? '');
if ($toName !== '') {
$message->to($log->to_email, $toName);
} else {
$message->to($log->to_email);
}
}
$message->subject($subject);
if (! empty($log->reply_to)) {
$message->replyTo($log->reply_to);
}
if (! empty($text)) {
// Provide a plain text alternative when available
$message->text($text);
}
});
} else {
\Mail::raw($text ?: '', function ($message) use ($log, $subject) {
$toList = (array) ($log->to_recipients ?? []);
if (! empty($toList)) {
$message->to($toList);
} else {
$toName = (string) ($log->to_name ?? '');
if ($toName !== '') {
$message->to($log->to_email, $toName);
} else {
$message->to($log->to_email);
}
}
$message->subject($subject);
if (! empty($log->reply_to)) {
$message->replyTo($log->reply_to);
}
});
}
}
return ['message_id' => $messageId];
}
}