Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SmtpEmailService
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 2
56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 send
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Notification\Service;
6
7use App\Domain\Notification\Data\EmailMessage;
8use App\Support\Row;
9use PHPMailer\PHPMailer\Exception as PHPMailerException;
10use PHPMailer\PHPMailer\PHPMailer;
11use RuntimeException;
12
13/**
14 * Sends emails via SMTP using PHPMailer.
15 *
16 * Configuration is injected from the 'email' settings block:
17 * - enabled: bool — when false, send() is a no-op (useful for dev/test)
18 * - from_address, from_name: default sender identity
19 * - smtp_host, smtp_port, smtp_username, smtp_password, smtp_encryption
20 */
21final readonly class SmtpEmailService implements EmailServiceInterface
22{
23    /**
24     * @param array<string, mixed> $config
25     */
26    public function __construct(
27        private array $config,
28    ) {}
29
30    public function send(EmailMessage $message): void
31    {
32        if (!Row::bool($this->config, 'enabled')) {
33            return;
34        }
35
36        $mail = new PHPMailer(true);
37
38        try {
39            $host = Row::string($this->config, 'smtp_host');
40            $username = Row::nullableString($this->config, 'smtp_username') ?? '';
41            $encryption = Row::nullableString($this->config, 'smtp_encryption') ?? '';
42
43            // Transport
44            if ($username !== '') {
45                $mail->isSMTP();
46                $mail->Host = $host;
47                $mail->Port = Row::int($this->config, 'smtp_port');
48                $mail->SMTPAuth = true;
49                $mail->Username = $username;
50                $mail->Password = Row::string($this->config, 'smtp_password');
51
52                if ($encryption !== '') {
53                    $mail->SMTPSecure = $encryption;
54                } else {
55                    $mail->SMTPSecure = '';
56                    $mail->SMTPAutoTLS = false;
57                }
58            } else {
59                // No auth — use SMTP without authentication (local mailhog, etc.)
60                $mail->isSMTP();
61                $mail->Host = $host;
62                $mail->Port = Row::int($this->config, 'smtp_port');
63                $mail->SMTPAuth = false;
64                $mail->SMTPSecure = '';
65                $mail->SMTPAutoTLS = false;
66            }
67
68            // Sender
69            $fromAddress = $message->from ?? Row::string($this->config, 'from_address');
70            $fromName = $message->fromName ?? Row::string($this->config, 'from_name');
71            $mail->setFrom($fromAddress, $fromName);
72
73            // Recipient
74            $mail->addAddress($message->to, $message->toName ?? '');
75
76            // Content
77            $mail->CharSet = 'UTF-8';
78            $mail->isHTML(true);
79            $mail->Subject = $message->subject;
80            $mail->Body = $message->bodyHtml;
81
82            if ($message->bodyText !== null) {
83                $mail->AltBody = $message->bodyText;
84            }
85
86            $mail->send();
87        } catch (PHPMailerException $e) {
88            throw new RuntimeException('Failed to send email: ' . $e->getMessage(), 0, $e);
89        }
90    }
91}