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 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
SendTestEmailCommand
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 3
30
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
 configure
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace App\Console;
6
7use App\Domain\Notification\Data\EmailMessage;
8use App\Domain\Notification\Service\EmailServiceInterface;
9use App\Support\Row;
10use Symfony\Component\Console\Command\Command;
11use Symfony\Component\Console\Input\InputArgument;
12use Symfony\Component\Console\Input\InputInterface;
13use Symfony\Component\Console\Output\OutputInterface;
14use Throwable;
15
16use function sprintf;
17
18/**
19 * Sends a single test email through the configured transport.
20 *
21 * Exercises the same path real app mail takes (RecordingEmailService →
22 * SmtpEmailService), so it verifies delivery end-to-end: in non-prod the
23 * message lands in that env's Mailpit, in prod it round-trips through the
24 * real relay to a live inbox. Intended as a pre-cutover smoke test.
25 *
26 * Usage:
27 *   php bin/console.php email:test you@example.com
28 *   php bin/console.php email:test you@example.com --env=prod
29 */
30final class SendTestEmailCommand extends Command
31{
32    /**
33     * @param array<string, mixed> $emailConfig the 'email' settings block
34     * @param EmailServiceInterface $emailService
35     * @param string $appEnv
36     */
37    public function __construct(
38        private readonly EmailServiceInterface $emailService,
39        private readonly array $emailConfig,
40        private readonly string $appEnv,
41    ) {
42        parent::__construct();
43    }
44
45    protected function configure(): void
46    {
47        parent::configure();
48
49        $this->setName('email:test');
50        $this->setDescription('Send a test email through the configured transport to verify deliverability');
51        $this->addArgument('to', InputArgument::REQUIRED, 'Recipient email address');
52    }
53
54    protected function execute(InputInterface $input, OutputInterface $output): int
55    {
56        /** @var string $to */
57        $to = $input->getArgument('to');
58
59        // SmtpEmailService.send() is a silent no-op when email.enabled is false,
60        // so without this guard the command would report success while sending
61        // nothing — the exact trap to avoid when validating a fresh prod relay.
62        if (!Row::bool($this->emailConfig, 'enabled')) {
63            $output->writeln(
64                "<error>email.enabled is false for this environment — no mail will be sent.</error>",
65            );
66            $output->writeln('Set $settings[\'email\'][\'enabled\'] = true in env.php and retry.');
67
68            return Command::FAILURE;
69        }
70
71        $message = new EmailMessage(
72            to: $to,
73            subject: sprintf('FlowState test email (%s)', $this->appEnv),
74            bodyHtml: sprintf(
75                '<p>This is a test email from the FlowState <strong>%s</strong> environment. '
76                . 'If you received this, SMTP delivery is working.</p>',
77                $this->appEnv,
78            ),
79            bodyText: sprintf(
80                'This is a test email from the FlowState %s environment. '
81                . 'If you received this, SMTP delivery is working.',
82                $this->appEnv,
83            ),
84            // Diagnostic, non-sensitive: allow the body into the email history.
85            sensitive: false,
86        );
87
88        $output->writeln(sprintf('<info>Sending test email to %s via the %s transport…</info>', $to, $this->appEnv));
89
90        try {
91            $this->emailService->send($message);
92        } catch (Throwable $e) {
93            $output->writeln(sprintf('<error>Send failed: %s</error>', $e->getMessage()));
94
95            return Command::FAILURE;
96        }
97
98        $output->writeln(
99            "<info>Sent. Confirm it arrived — in non-prod check this env's Mailpit inbox; "
100            . 'in prod check the recipient mailbox.</info>',
101        );
102
103        return Command::SUCCESS;
104    }
105}