Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Classes/Transport/AbstractTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace FormatD\Mailer\Transport;

use Neos\Flow\Annotations as Flow;
use Symfony\Component\Mailer\Transport\TransportInterface;

abstract class AbstractTransport implements TransportInterface
{
#[Flow\InjectConfiguration(package: 'FormatD.Mailer', type: 'Settings')]
protected array $settings;

const string DEFAULT_INTERCEPT_DATA_HEADER_NAME = 'X-Intercept-Metadata';
}
50 changes: 45 additions & 5 deletions Classes/Transport/FdMailerTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
namespace FormatD\Mailer\Transport;

use Neos\Flow\Annotations as Flow;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mime\RawMessage;
use Symfony\Component\Mime\Email;

class FdMailerTransport implements TransportInterface
class FdMailerTransport extends AbstractTransport
{
#[Flow\InjectConfiguration(package: 'FormatD.Mailer', type: 'Settings')]
protected array $settings;

protected ?TransportInterface $actualTransport = null;

#[Flow\Inject(name: "FormatD.Mailer:MailerLogger", lazy: false)]
protected LoggerInterface $logger;

/**
* @throws TransportExceptionInterface|\Exception
*/
Expand All @@ -25,7 +27,12 @@ public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMess
if ($this->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;
}

/**
Expand Down Expand Up @@ -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]));
}
}
14 changes: 10 additions & 4 deletions Classes/Transport/InterceptingTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions Configuration/Objects.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'FormatD.Mailer:MailerLogger':
className: Psr\Log\LoggerInterface
scope: singleton
factoryObjectName: Neos\Flow\Log\PsrLoggerFactoryInterface
factoryMethodName: get
arguments:
1:
value: MailerLogger
14 changes: 14 additions & 0 deletions Configuration/Settings.Neos.Flow.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ FormatD:
active: false
recipients: []
noInterceptPatterns: []
interceptDataHeaderName: 'X-Intercept-Metadata'

bccAll:
active: false
recipients: []
defaultFrom:
address: 'example@example.com'
name: 'Example'
name: 'Example'
logging:
emailDispatch: true
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down