diff --git a/Classes/Transport/AbstractTransport.php b/Classes/Transport/AbstractTransport.php new file mode 100644 index 0000000..46f237e --- /dev/null +++ b/Classes/Transport/AbstractTransport.php @@ -0,0 +1,14 @@ +actualTransport === null) { $this->actualTransport = Transport::fromDsn($this->getActualTransportDsn()); } - return $this->actualTransport->send($message); + + $sentMessage = $this->actualTransport->send($message, $envelope); + + $this->logEmailDispatch($sentMessage, $message); + + return $sentMessage; } /** @@ -66,4 +73,37 @@ public function __toString(): string { return 'fd-mailer'; } + + /** + * @throws \JsonException + */ + protected function logEmailDispatch(?SentMessage $sentMessage, RawMessage $message): void + { + $isLoggable = $sentMessage instanceof SentMessage + && $message instanceof Email + && $this->settings['logging']['emailDispatch']; + + if (!$isLoggable) { + return; + } + + $interceptDataHeaderName = $this->settings['interceptAll']['interceptDataHeaderName'] ?? self::DEFAULT_INTERCEPT_DATA_HEADER_NAME; + $isIntercepted = $message->getHeaders()->has($interceptDataHeaderName); + if ($isIntercepted) { + $interceptMetaData = json_decode($message->getHeaders()->get($interceptDataHeaderName)->getBody(), true, 16, JSON_THROW_ON_ERROR); + } + + $subject = !$isIntercepted ? $message->getSubject() : $interceptMetaData['subject']; + if (mb_strlen($subject) > 50) { + $subject = mb_substr($subject, 0, 50) . ' …'; + } + $from = !$isIntercepted + ? implode(', ', array_map(fn($a) => $a->toString(), $message->getFrom())) + : implode(', ', $interceptMetaData['from']); + $to = !$isIntercepted + ? implode(', ', array_map(fn($a) => $a->toString(), $message->getTo())) + : implode(', ', $interceptMetaData['to']); + + $this->logger->info(vsprintf('Sent email to %s (from %s) with subject "%s"', [$to, $from, $subject])); + } } diff --git a/Classes/Transport/InterceptingTransport.php b/Classes/Transport/InterceptingTransport.php index ecd5fb7..936347a 100644 --- a/Classes/Transport/InterceptingTransport.php +++ b/Classes/Transport/InterceptingTransport.php @@ -9,11 +9,8 @@ use Symfony\Component\Mime\Email; use Symfony\Component\Mime\RawMessage; -class InterceptingTransport implements TransportInterface +class InterceptingTransport extends AbstractTransport { - #[Flow\InjectConfiguration(package: 'FormatD.Mailer', type: 'Settings')] - protected array $settings; - public function __construct(protected TransportInterface $actualTransport) { } @@ -55,6 +52,15 @@ protected function interceptMessage(Email $message): Email $message->getHeaders()->remove('Cc'); $message->getHeaders()->remove('Bcc'); + $interceptMetaData = [ + 'to' => array_map(fn($a) => $a->toString(), $message->getTo()), + 'from' => array_map(fn($a) => $a->toString(), $message->getFrom()), + 'subject' => $message->getSubject(), + ]; + + $interceptDataHeaderName = $this->settings['interceptAll']['interceptDataHeaderName'] ?? self::DEFAULT_INTERCEPT_DATA_HEADER_NAME; + $message->getHeaders()->addTextHeader($interceptDataHeaderName, \json_encode($interceptMetaData)); + $first = true; foreach ($this->settings['interceptAll']['recipients'] as $email) { if ($first) { diff --git a/Configuration/Objects.yaml b/Configuration/Objects.yaml new file mode 100644 index 0000000..22cb4d0 --- /dev/null +++ b/Configuration/Objects.yaml @@ -0,0 +1,8 @@ +'FormatD.Mailer:MailerLogger': + className: Psr\Log\LoggerInterface + scope: singleton + factoryObjectName: Neos\Flow\Log\PsrLoggerFactoryInterface + factoryMethodName: get + arguments: + 1: + value: MailerLogger diff --git a/Configuration/Settings.Neos.Flow.yaml b/Configuration/Settings.Neos.Flow.yaml new file mode 100644 index 0000000..be02e8b --- /dev/null +++ b/Configuration/Settings.Neos.Flow.yaml @@ -0,0 +1,14 @@ +Neos: + Flow: + log: + psr3: + Neos\Flow\Log\PsrLoggerFactory: + MailerLogger: + default: + class: Neos\Flow\Log\Backend\FileBackend + options: + logFileURL: '%FLOW_PATH_DATA%Logs/Mailer.log' + severityThreshold: '%LOG_INFO%' + createParentDirectories: true + maximumLogFileSize: 1048576 + logFilesToKeep: 1 diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 859bc28..02892f4 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -17,9 +17,13 @@ FormatD: active: false recipients: [] noInterceptPatterns: [] + interceptDataHeaderName: 'X-Intercept-Metadata' + bccAll: active: false recipients: [] defaultFrom: address: 'example@example.com' - name: 'Example' \ No newline at end of file + name: 'Example' + logging: + emailDispatch: true diff --git a/README.md b/README.md index dd3198e..9188221 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ FormatD: Extend AbstractMailerService and add methods as needed following the example of sendTestMail(). -## intersept all mails in a dev environment +## intercept all mails in a dev environment -Configure swiftmailer to intersept all mails send by your neos installation (not only by the service). +Configure the mailer to intercept all mails send by your neos installation (not only by the service). This is an example which intercepts all mails and redirects them to example@example.com and secondexample@example.com: @@ -58,6 +58,30 @@ FormatD: recipients: [] ``` +## Logging email dispatch + +Every sent email (to, from, subject) is logged via its own logger channel `FormatD.Mailer:MailerLogger`, writing to +`Data/Logs/Mailer.log`. This can be disabled: + +``` +FormatD: + Mailer: + logging: + emailDispatch: false +``` + +If a mail was intercepted (see `interceptAll`/`bccAll` above), the log entry still shows the *original* recipient, +sender and subject — not the intercepted ones — since those are the actually relevant values for debugging. This is +achieved by stashing the original data in an `X-Intercept-Metadata` mail header before rewriting the message. The +header name can be changed if it collides with something: + +``` +FormatD: + Mailer: + interceptAll: + interceptDataHeaderName: 'X-Intercept-Metadata' +``` + ## Handling Embedded Images The method `AbstractMailerService->setMailContentFromStandaloneView()` has a parameter to embed all images into the mail