Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.33% covered (danger)
43.33%
13 / 30
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
EmailChannel
43.33% covered (danger)
43.33%
13 / 30
60.00% covered (warning)
60.00%
3 / 5
28.20
0.00% covered (danger)
0.00%
0 / 1
 key
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 label
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configSchema
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 send
11.11% covered (danger)
11.11%
2 / 18
0.00% covered (danger)
0.00%
0 / 1
31.28
 isImplemented
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Services\Notification\Channels;
4
5use App\Services\Notification\Contracts\NotificationChannelContract;
6use Symfony\Component\Mailer\Mailer;
7use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
8use Symfony\Component\Mime\Address;
9use Symfony\Component\Mime\Email;
10
11/**
12 * Every provider in the spec (SMTP / Mailgun / SES / SendGrid / Resend) also
13 * offers a plain SMTP relay, so one SMTP sender covers all five without
14 * installing a separate bridge package per provider — the operator just
15 * pastes whichever provider's SMTP host/credentials. Built directly on
16 * symfony/mailer (already a Laravel dependency) rather than reconfiguring
17 * the app's own config/mail.php at runtime, so this never touches the
18 * mailer Laravel itself uses for password-reset emails etc.
19 */
20class EmailChannel implements NotificationChannelContract
21{
22    public function key(): string
23    {
24        return 'email';
25    }
26
27    public function label(): string
28    {
29        return 'Email';
30    }
31
32    public function configSchema(): array
33    {
34        return [
35            'host' => ['type' => 'text', 'label' => 'SMTP host', 'required' => true, 'help' => 'Works with any provider\'s SMTP relay — Mailgun, SES, SendGrid, Resend, or your own mail server.'],
36            'port' => ['type' => 'text', 'label' => 'Port', 'required' => true],
37            'encryption' => ['type' => 'text', 'label' => 'Encryption (tls or ssl)', 'required' => false],
38            'username' => ['type' => 'text', 'label' => 'Username', 'required' => true],
39            'password' => ['type' => 'password', 'label' => 'Password', 'required' => true],
40            'from_address' => ['type' => 'text', 'label' => 'From address', 'required' => true],
41            'from_name' => ['type' => 'text', 'label' => 'From name', 'required' => false],
42        ];
43    }
44
45    public function send(string $to, string $subject, string $body, array $config): array
46    {
47        if (empty($config['host']) || empty($config['username']) || empty($config['password']) || empty($config['from_address'])) {
48            return ['ok' => false, 'response' => 'Email is not configured — fill in the SMTP fields in Settings → Notification Settings.'];
49        }
50
51        try {
52            $transport = new EsmtpTransport(
53                $config['host'],
54                (int) ($config['port'] ?? 587),
55                strtolower((string) ($config['encryption'] ?? '')) === 'ssl',
56            );
57            $transport->setUsername($config['username']);
58            $transport->setPassword($config['password']);
59
60            $email = (new Email)
61                ->from(new Address($config['from_address'], $config['from_name'] ?? ''))
62                ->to($to)
63                ->subject($subject)
64                ->text($body);
65
66            (new Mailer($transport))->send($email);
67
68            return ['ok' => true, 'response' => 'Sent'];
69        } catch (\Throwable $e) {
70            return ['ok' => false, 'response' => $e->getMessage()];
71        }
72    }
73
74    public function isImplemented(): bool
75    {
76        return true;
77    }
78}