From db7edda9119f79fd07c8889e52f795b863f0639f Mon Sep 17 00:00:00 2001
From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com>
Date: Thu, 1 Feb 2024 03:31:17 +0100
Subject: [PATCH] feat: mailer

---
 config.ini.example                            |  12 +-
 src/DatabaseConfiguration.php                 |   2 +-
 src/DoctrineConnectionRepository.php          |   9 ++
 src/DoctrineConsoleEntityManagerProvider.php  |   5 +
 src/DoctrineEntityManagerRepository.php       |  14 +++
 src/Mailer.php                                |  40 +++++++
 src/MailerConfiguration.php                   |  24 ++--
 src/MailerRepository.php                      |  20 ++++
 src/MailerTransportConfiguration.php          |  28 +++++
 src/ServerTask.php                            |   7 ++
 src/ServerTask/SendEmailMessage.php           |  18 +++
 src/ServerTaskHandler/SendsEmailMessage.php   |  30 ++---
 .../DatabaseConfigurationProvider.php         |   2 +-
 .../LlamaCppConfigurationProvider.php         |   2 +-
 .../MailerConfigurationProvider.php           |  62 +++++++----
 src/SingletonProvider/DkimSignerProvider.php  |  33 ------
 src/SingletonProvider/MailerProvider.php      |  37 -------
 .../MailerRepositoryProvider.php              | 104 ++++++++++++++++++
 src/SingletonProvider/TransportProvider.php   |  40 -------
 19 files changed, 311 insertions(+), 178 deletions(-)
 create mode 100644 src/Mailer.php
 create mode 100644 src/MailerRepository.php
 create mode 100644 src/MailerTransportConfiguration.php
 create mode 100644 src/ServerTask.php
 create mode 100644 src/ServerTask/SendEmailMessage.php
 delete mode 100644 src/SingletonProvider/DkimSignerProvider.php
 delete mode 100644 src/SingletonProvider/MailerProvider.php
 create mode 100644 src/SingletonProvider/MailerRepositoryProvider.php
 delete mode 100644 src/SingletonProvider/TransportProvider.php

diff --git a/config.ini.example b/config.ini.example
index 2bd440ae..05343201 100644
--- a/config.ini.example
+++ b/config.ini.example
@@ -20,12 +20,12 @@ host = 127.0.0.1
 port = 8081
 
 [mailer]
-transport_dsn = smtp://localhost
-dkim_domain_name = example.com
-dkim_selector = selector
-dkim_signing_key_passphrase = yourpassphrase
-dkim_signing_key_private = dkim/private.key
-dkim_signing_key_public = dkim/public.key
+default[transport_dsn] = smtp://localhost
+default[dkim_domain_name] = example.com
+default[dkim_selector] = resonance1
+default[dkim_signing_key_passphrase] = yourpassphrase
+default[dkim_signing_key_private] = dkim/private.key
+default[dkim_signing_key_public] = dkim/public.key
 
 [manifest]
 background_color = "#ffffff"
diff --git a/src/DatabaseConfiguration.php b/src/DatabaseConfiguration.php
index 1a8d9883..bdc8b87b 100644
--- a/src/DatabaseConfiguration.php
+++ b/src/DatabaseConfiguration.php
@@ -9,7 +9,7 @@ use Ds\Map;
 readonly class DatabaseConfiguration
 {
     /**
-     * @var Map<string,DatabaseConnectionPoolConfiguration>
+     * @var Map<non-empty-string,DatabaseConnectionPoolConfiguration>
      */
     public Map $connectionPoolConfiguration;
 
diff --git a/src/DoctrineConnectionRepository.php b/src/DoctrineConnectionRepository.php
index c4bd3317..ee44b12b 100644
--- a/src/DoctrineConnectionRepository.php
+++ b/src/DoctrineConnectionRepository.php
@@ -62,6 +62,9 @@ readonly class DoctrineConnectionRepository extends EventListener
         $this->eventListenerAggregate->removeListener(HttpResponseReady::class, $this);
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     public function buildConnection(string $name = 'default'): Connection
     {
         return new Connection(
@@ -75,6 +78,9 @@ readonly class DoctrineConnectionRepository extends EventListener
         );
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     public function getConnection(Request $request, string $name = 'default'): Connection
     {
         if (!$this->connections->offsetExists($request)) {
@@ -124,6 +130,9 @@ readonly class DoctrineConnectionRepository extends EventListener
         }
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     private function getDriver(string $name): Driver
     {
         $poolConfiguration = $this->databaseConfiguration->connectionPoolConfiguration->get($name);
diff --git a/src/DoctrineConsoleEntityManagerProvider.php b/src/DoctrineConsoleEntityManagerProvider.php
index 4f22f641..4fe7d27f 100644
--- a/src/DoctrineConsoleEntityManagerProvider.php
+++ b/src/DoctrineConsoleEntityManagerProvider.php
@@ -9,6 +9,7 @@ use Doctrine\ORM\Configuration;
 use Doctrine\ORM\EntityManager;
 use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\ORM\Tools\Console\EntityManagerProvider;
+use RuntimeException;
 
 #[Singleton]
 readonly class DoctrineConsoleEntityManagerProvider implements EntityManagerProvider
@@ -25,6 +26,10 @@ readonly class DoctrineConsoleEntityManagerProvider implements EntityManagerProv
 
     public function getManager(string $name): EntityManagerInterface
     {
+        if (empty($name)) {
+            throw new RuntimeException('Connection pool name must be a non-empty string');
+        }
+
         return new EntityManager(
             $this->doctrineConnectionRepository->buildConnection($name),
             $this->configuration,
diff --git a/src/DoctrineEntityManagerRepository.php b/src/DoctrineEntityManagerRepository.php
index 3398972e..92b658f5 100644
--- a/src/DoctrineEntityManagerRepository.php
+++ b/src/DoctrineEntityManagerRepository.php
@@ -32,6 +32,9 @@ readonly class DoctrineEntityManagerRepository
         $this->entityManagers = new WeakMap();
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     public function buildEntityManager(string $name = 'default'): EntityManagerInterface
     {
         return new EntityManager(
@@ -40,11 +43,17 @@ readonly class DoctrineEntityManagerRepository
         );
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     public function createContextKey(string $name): string
     {
         return sprintf('%s.%s', self::class, $name);
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     public function getEntityManager(Request $request, string $name = 'default'): EntityManagerInterface
     {
         if (!$this->entityManagers->offsetExists($request)) {
@@ -83,6 +92,7 @@ readonly class DoctrineEntityManagerRepository
      * @template TCallbackReturn
      *
      * @param callable(EntityManagerInterface):TCallbackReturn $callback
+     * @param non-empty-string                                 $name
      *
      * @return TCallbackReturn
      */
@@ -126,6 +136,7 @@ readonly class DoctrineEntityManagerRepository
      *
      * @param class-string<TEntityClass>                                         $className
      * @param callable(EntityManagerInterface,TEntityRepository):TCallbackReturn $callback
+     * @param non-empty-string                                                   $name
      *
      * @return TCallbackReturn
      */
@@ -141,6 +152,9 @@ readonly class DoctrineEntityManagerRepository
         }, $name, $flush);
     }
 
+    /**
+     * @param non-empty-string $name
+     */
     private function getWeakReference(string $name = 'default'): EntityManagerWeakReference
     {
         return new EntityManagerWeakReference($this->buildEntityManager($name));
diff --git a/src/Mailer.php b/src/Mailer.php
new file mode 100644
index 00000000..c22eb035
--- /dev/null
+++ b/src/Mailer.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Distantmagic\Resonance\ServerTask\SendEmailMessage;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Mime\Crypto\DkimSigner;
+use Symfony\Component\Mime\Message;
+
+readonly class Mailer
+{
+    /**
+     * @param non-empty-string $name
+     */
+    public function __construct(
+        private ?DkimSigner $dkimSigner,
+        private ?MessageBusInterface $messageBus,
+        private string $name,
+        private TransportInterface $transport,
+    ) {}
+
+    public function enqueue(Message $message): void
+    {
+        if ($this->messageBus) {
+            $this->messageBus->dispatch(new SendEmailMessage($this->name, $message));
+        } else {
+            $this->send($message);
+        }
+    }
+
+    public function send(Message $message): void
+    {
+        $this->transport->send(
+            $this->dkimSigner ? $this->dkimSigner->sign($message) : $message
+        );
+    }
+}
diff --git a/src/MailerConfiguration.php b/src/MailerConfiguration.php
index ab5cbaf5..e2121d03 100644
--- a/src/MailerConfiguration.php
+++ b/src/MailerConfiguration.php
@@ -4,25 +4,17 @@ declare(strict_types=1);
 
 namespace Distantmagic\Resonance;
 
-use SensitiveParameter;
+use Ds\Map;
 
 readonly class MailerConfiguration
 {
     /**
-     * @param non-empty-string $transportDsn
+     * @var Map<non-empty-string,MailerTransportConfiguration>
      */
-    public function __construct(
-        #[SensitiveParameter]
-        public string $dkimDomainName,
-        #[SensitiveParameter]
-        public string $dkimSelector,
-        #[SensitiveParameter]
-        public string $dkimSigningKeyPassphrase,
-        #[SensitiveParameter]
-        public string $dkimSigningKeyPrivate,
-        #[SensitiveParameter]
-        public string $dkimSigningKeyPublic,
-        #[SensitiveParameter]
-        public string $transportDsn,
-    ) {}
+    public Map $transportConfiguration;
+
+    public function __construct()
+    {
+        $this->transportConfiguration = new Map();
+    }
 }
diff --git a/src/MailerRepository.php b/src/MailerRepository.php
new file mode 100644
index 00000000..89c1a8ba
--- /dev/null
+++ b/src/MailerRepository.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Ds\Map;
+
+readonly class MailerRepository
+{
+    /**
+     * @var Map<non-empty-string,Mailer>
+     */
+    public Map $mailer;
+
+    public function __construct()
+    {
+        $this->mailer = new Map();
+    }
+}
diff --git a/src/MailerTransportConfiguration.php b/src/MailerTransportConfiguration.php
new file mode 100644
index 00000000..daa54fdc
--- /dev/null
+++ b/src/MailerTransportConfiguration.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use SensitiveParameter;
+
+readonly class MailerTransportConfiguration
+{
+    /**
+     * @param non-empty-string $transportDsn
+     */
+    public function __construct(
+        #[SensitiveParameter]
+        public ?string $dkimDomainName,
+        #[SensitiveParameter]
+        public ?string $dkimSelector,
+        #[SensitiveParameter]
+        public ?string $dkimSigningKeyPassphrase,
+        #[SensitiveParameter]
+        public ?string $dkimSigningKeyPrivate,
+        #[SensitiveParameter]
+        public ?string $dkimSigningKeyPublic,
+        #[SensitiveParameter]
+        public string $transportDsn,
+    ) {}
+}
diff --git a/src/ServerTask.php b/src/ServerTask.php
new file mode 100644
index 00000000..0266da9b
--- /dev/null
+++ b/src/ServerTask.php
@@ -0,0 +1,7 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+abstract readonly class ServerTask {}
diff --git a/src/ServerTask/SendEmailMessage.php b/src/ServerTask/SendEmailMessage.php
new file mode 100644
index 00000000..7c50badd
--- /dev/null
+++ b/src/ServerTask/SendEmailMessage.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\ServerTask;
+
+use Symfony\Component\Mime\Message;
+
+readonly class SendEmailMessage
+{
+    /**
+     * @param non-empty-string $transportName
+     */
+    public function __construct(
+        public string $transportName,
+        public Message $message,
+    ) {}
+}
diff --git a/src/ServerTaskHandler/SendsEmailMessage.php b/src/ServerTaskHandler/SendsEmailMessage.php
index a56f14c7..776f0f4b 100644
--- a/src/ServerTaskHandler/SendsEmailMessage.php
+++ b/src/ServerTaskHandler/SendsEmailMessage.php
@@ -8,13 +8,10 @@ use Distantmagic\Resonance\Attribute\GrantsFeature;
 use Distantmagic\Resonance\Attribute\HandlesServerTask;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\Feature;
+use Distantmagic\Resonance\MailerRepository;
+use Distantmagic\Resonance\ServerTask\SendEmailMessage;
 use Distantmagic\Resonance\ServerTaskHandler;
 use Distantmagic\Resonance\SingletonCollection;
-use RuntimeException;
-use Symfony\Component\Mailer\Messenger\SendEmailMessage;
-use Symfony\Component\Mailer\Transport\TransportInterface;
-use Symfony\Component\Mime\Crypto\DkimSigner;
-use Symfony\Component\Mime\Message;
 
 /**
  * @template-extends ServerTaskHandler<SendEmailMessage>
@@ -25,25 +22,16 @@ use Symfony\Component\Mime\Message;
 readonly class SendsEmailMessage extends ServerTaskHandler
 {
     public function __construct(
-        private DkimSigner $dkimSigner,
-        private TransportInterface $transport,
+        private MailerRepository $mailerRepository
     ) {}
 
     public function handleServerTask(object $serverTask): void
     {
-        $email = $serverTask->getMessage();
-
-        if (!($email instanceof Message)) {
-            throw new RuntimeException(sprintf(
-                'Expected instanceof "%s", got "%s"',
-                Message::class,
-                $email::class,
-            ));
-        }
-
-        $this->transport->send(
-            $this->dkimSigner->sign($email),
-            $serverTask->getEnvelope(),
-        );
+        $this
+            ->mailerRepository
+            ->mailer
+            ->get($serverTask->transportName)
+            ->send($serverTask->message)
+        ;
     }
 }
diff --git a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
index b2e277cb..d7cb2e2d 100644
--- a/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
+++ b/src/SingletonProvider/ConfigurationProvider/DatabaseConfigurationProvider.php
@@ -14,7 +14,7 @@ use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider;
 /**
  * @template-extends ConfigurationProvider<
  *     DatabaseConfiguration,
- *     array<string, object{
+ *     array<non-empty-string, object{
  *         database: string,
  *         driver: string,
  *         host: non-empty-string,
diff --git a/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php
index 468f669d..fad86799 100644
--- a/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php
+++ b/src/SingletonProvider/ConfigurationProvider/LlamaCppConfigurationProvider.php
@@ -27,7 +27,7 @@ final readonly class LlamaCppConfigurationProvider extends ConfigurationProvider
             'type' => 'object',
             'properties' => [
                 'api_key' => [
-                    'type' => ['null', 'string'],
+                    'type' => 'string',
                     'minLength' => 1,
                     'default' => null,
                 ],
diff --git a/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php b/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php
index feb80c48..34504416 100644
--- a/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php
+++ b/src/SingletonProvider/ConfigurationProvider/MailerConfigurationProvider.php
@@ -7,45 +7,54 @@ namespace Distantmagic\Resonance\SingletonProvider\ConfigurationProvider;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\JsonSchema;
 use Distantmagic\Resonance\MailerConfiguration;
+use Distantmagic\Resonance\MailerTransportConfiguration;
 use Distantmagic\Resonance\SingletonProvider\ConfigurationProvider;
 
 /**
- * @template-extends ConfigurationProvider<MailerConfiguration, object{
- *     dkim_domain_name: non-empty-string,
- *     dkim_selector: non-empty-string,
- *     dkim_signing_key_passphrase: non-empty-string,
- *     dkim_signing_key_private: non-empty-string,
- *     dkim_signing_key_public: non-empty-string,
- *     transport_dsn: non-empty-string,
- * }>
+ * @template-extends ConfigurationProvider<
+ *     MailerConfiguration,
+ *     array<non-empty-string, object{
+ *         dkim_domain_name: null|non-empty-string,
+ *         dkim_selector: null|non-empty-string,
+ *         dkim_signing_key_passphrase: null|non-empty-string,
+ *         dkim_signing_key_private: null|non-empty-string,
+ *         dkim_signing_key_public: null|non-empty-string,
+ *         transport_dsn: non-empty-string,
+ *     }>
+ * >
  */
 #[Singleton(provides: MailerConfiguration::class)]
 final readonly class MailerConfigurationProvider extends ConfigurationProvider
 {
     public function getSchema(): JsonSchema
     {
-        return new JsonSchema([
+        $valueSchema = [
             'type' => 'object',
             'properties' => [
                 'dkim_domain_name' => [
                     'type' => 'string',
                     'minLength' => 1,
+                    'default' => null,
                 ],
                 'dkim_selector' => [
                     'type' => 'string',
                     'minLength' => 1,
+                    'default' => null,
                 ],
                 'dkim_signing_key_passphrase' => [
                     'type' => 'string',
                     'minLength' => 1,
+                    'default' => null,
                 ],
                 'dkim_signing_key_private' => [
                     'type' => 'string',
                     'minLength' => 1,
+                    'default' => null,
                 ],
                 'dkim_signing_key_public' => [
                     'type' => 'string',
                     'minLength' => 1,
+                    'default' => null,
                 ],
                 'transport_dsn' => [
                     'type' => 'string',
@@ -53,13 +62,13 @@ final readonly class MailerConfigurationProvider extends ConfigurationProvider
                 ],
             ],
             'required' => [
-                'dkim_domain_name',
-                'dkim_selector',
-                'dkim_signing_key_passphrase',
-                'dkim_signing_key_private',
-                'dkim_signing_key_public',
                 'transport_dsn',
             ],
+        ];
+
+        return new JsonSchema([
+            'type' => 'object',
+            'additionalProperties' => $valueSchema,
         ]);
     }
 
@@ -70,13 +79,22 @@ final readonly class MailerConfigurationProvider extends ConfigurationProvider
 
     protected function provideConfiguration($validatedData): MailerConfiguration
     {
-        return new MailerConfiguration(
-            dkimDomainName: $validatedData->dkim_domain_name,
-            dkimSelector: $validatedData->dkim_selector,
-            dkimSigningKeyPassphrase: $validatedData->dkim_signing_key_passphrase,
-            dkimSigningKeyPrivate: $validatedData->dkim_signing_key_private,
-            dkimSigningKeyPublic: $validatedData->dkim_signing_key_public,
-            transportDsn: $validatedData->transport_dsn,
-        );
+        $mailerConfiguration = new MailerConfiguration();
+
+        foreach ($validatedData as $name => $transportConfiguration) {
+            $mailerConfiguration->transportConfiguration->put(
+                $name,
+                new MailerTransportConfiguration(
+                    dkimDomainName: $transportConfiguration->dkim_domain_name,
+                    dkimSelector: $transportConfiguration->dkim_selector,
+                    dkimSigningKeyPassphrase: $transportConfiguration->dkim_signing_key_passphrase,
+                    dkimSigningKeyPrivate: $transportConfiguration->dkim_signing_key_private,
+                    dkimSigningKeyPublic: $transportConfiguration->dkim_signing_key_public,
+                    transportDsn: $transportConfiguration->transport_dsn,
+                )
+            );
+        }
+
+        return $mailerConfiguration;
     }
 }
diff --git a/src/SingletonProvider/DkimSignerProvider.php b/src/SingletonProvider/DkimSignerProvider.php
deleted file mode 100644
index 0b0246c0..00000000
--- a/src/SingletonProvider/DkimSignerProvider.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Distantmagic\Resonance\SingletonProvider;
-
-use Distantmagic\Resonance\Attribute\Singleton;
-use Distantmagic\Resonance\MailerConfiguration;
-use Distantmagic\Resonance\PHPProjectFiles;
-use Distantmagic\Resonance\SingletonContainer;
-use Distantmagic\Resonance\SingletonProvider;
-use Symfony\Component\Mime\Crypto\DkimSigner;
-
-/**
- * @template-extends SingletonProvider<DkimSigner>
- */
-#[Singleton(provides: DkimSigner::class)]
-final readonly class DkimSignerProvider extends SingletonProvider
-{
-    public function __construct(
-        private MailerConfiguration $mailerConfiguration,
-    ) {}
-
-    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): DkimSigner
-    {
-        return new DkimSigner(
-            domainName: $this->mailerConfiguration->dkimDomainName,
-            passphrase: $this->mailerConfiguration->dkimSigningKeyPassphrase,
-            pk: 'file://'.$this->mailerConfiguration->dkimSigningKeyPrivate,
-            selector: $this->mailerConfiguration->dkimSelector,
-        );
-    }
-}
diff --git a/src/SingletonProvider/MailerProvider.php b/src/SingletonProvider/MailerProvider.php
deleted file mode 100644
index 277d8fac..00000000
--- a/src/SingletonProvider/MailerProvider.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Distantmagic\Resonance\SingletonProvider;
-
-use Distantmagic\Resonance\Attribute\Singleton;
-use Distantmagic\Resonance\EventDispatcherInterface;
-use Distantmagic\Resonance\PHPProjectFiles;
-use Distantmagic\Resonance\SingletonContainer;
-use Distantmagic\Resonance\SingletonProvider;
-use Distantmagic\Resonance\SwooleTaskServerMessageBus;
-use Symfony\Component\Mailer\Mailer;
-use Symfony\Component\Mailer\MailerInterface;
-use Symfony\Component\Mailer\Transport\TransportInterface;
-
-/**
- * @template-extends SingletonProvider<MailerInterface>
- */
-#[Singleton(provides: MailerInterface::class)]
-final readonly class MailerProvider extends SingletonProvider
-{
-    public function __construct(
-        private EventDispatcherInterface $eventDispatcher,
-        private TransportInterface $transport,
-        private ?SwooleTaskServerMessageBus $swooleTaskServerMessageBus = null,
-    ) {}
-
-    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): MailerInterface
-    {
-        return new Mailer(
-            bus: $this->swooleTaskServerMessageBus,
-            dispatcher: $this->eventDispatcher,
-            transport: $this->transport,
-        );
-    }
-}
diff --git a/src/SingletonProvider/MailerRepositoryProvider.php b/src/SingletonProvider/MailerRepositoryProvider.php
new file mode 100644
index 00000000..97d0e572
--- /dev/null
+++ b/src/SingletonProvider/MailerRepositoryProvider.php
@@ -0,0 +1,104 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance\SingletonProvider;
+
+use Distantmagic\Resonance\Attribute\Singleton;
+use Distantmagic\Resonance\EventDispatcherInterface;
+use Distantmagic\Resonance\Mailer;
+use Distantmagic\Resonance\MailerConfiguration;
+use Distantmagic\Resonance\MailerRepository;
+use Distantmagic\Resonance\MailerTransportConfiguration;
+use Distantmagic\Resonance\PHPProjectFiles;
+use Distantmagic\Resonance\SingletonContainer;
+use Distantmagic\Resonance\SingletonProvider;
+use Distantmagic\Resonance\SwooleTaskServerMessageBus;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Symfony\Component\Mailer\Transport;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Mime\Crypto\DkimSigner;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * @template-extends SingletonProvider<MailerRepository>
+ */
+#[Singleton(provides: MailerRepository::class)]
+final readonly class MailerRepositoryProvider extends SingletonProvider
+{
+    public function __construct(
+        private EventDispatcherInterface $eventDispatcher,
+        private HttpClientInterface $httpClient,
+        private LoggerInterface $logger,
+        private MailerConfiguration $mailerConfiguration,
+        private ?SwooleTaskServerMessageBus $swooleTaskServerMessageBus = null,
+    ) {}
+
+    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): MailerRepository
+    {
+        $mailerRepository = new MailerRepository();
+
+        foreach ($this->mailerConfiguration->transportConfiguration as $name => $transportConfiguration) {
+            $mailerRepository->mailer->put(
+                $name,
+                new Mailer(
+                    dkimSigner: $this->buildDkimSigner($name, $transportConfiguration),
+                    name: $name,
+                    messageBus: $this->swooleTaskServerMessageBus,
+                    transport: $this->buildTransport($transportConfiguration),
+                )
+            );
+        }
+
+        return $mailerRepository;
+    }
+
+    /**
+     * @param non-empty-string $name
+     */
+    private function buildDkimSigner(string $name, MailerTransportConfiguration $transportConfiguration): ?DkimSigner
+    {
+        if (isset(
+            $transportConfiguration->dkimDomainName,
+            $transportConfiguration->dkimSigningKeyPassphrase,
+            $transportConfiguration->dkimSigningKeyPrivate,
+            $transportConfiguration->dkimSelector,
+        )) {
+            return new DkimSigner(
+                domainName: $transportConfiguration->dkimDomainName,
+                passphrase: $transportConfiguration->dkimSigningKeyPassphrase,
+                pk: 'file://'.$transportConfiguration->dkimSigningKeyPrivate,
+                selector: $transportConfiguration->dkimSelector,
+            );
+        }
+
+        if (
+            is_null($transportConfiguration->dkimDomainName)
+            && is_null($transportConfiguration->dkimSigningKeyPassphrase)
+            && is_null($transportConfiguration->dkimSigningKeyPrivate)
+            && is_null($transportConfiguration->dkimSelector)
+        ) {
+            return null;
+        }
+
+        throw new RuntimeException(sprintf(
+            <<<'ERROR_MESSAGE'
+            If you want to use DKIM you need to fill all the DKIM settings.
+            If you wan to disable DKIM for this transport, do not fill any DKIM
+            settigns: "%s"
+            ERROR_MESSAGE,
+            $name,
+        ));
+    }
+
+    private function buildTransport(MailerTransportConfiguration $transportConfiguration): TransportInterface
+    {
+        return Transport::fromDsn(
+            client: $this->httpClient,
+            dispatcher: $this->eventDispatcher,
+            dsn: $transportConfiguration->transportDsn,
+            logger: $this->logger,
+        );
+    }
+}
diff --git a/src/SingletonProvider/TransportProvider.php b/src/SingletonProvider/TransportProvider.php
deleted file mode 100644
index f591229d..00000000
--- a/src/SingletonProvider/TransportProvider.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Distantmagic\Resonance\SingletonProvider;
-
-use Distantmagic\Resonance\Attribute\Singleton;
-use Distantmagic\Resonance\EventDispatcherInterface;
-use Distantmagic\Resonance\MailerConfiguration;
-use Distantmagic\Resonance\PHPProjectFiles;
-use Distantmagic\Resonance\SingletonContainer;
-use Distantmagic\Resonance\SingletonProvider;
-use Psr\Log\LoggerInterface;
-use Symfony\Component\Mailer\Transport;
-use Symfony\Component\Mailer\Transport\TransportInterface;
-use Symfony\Contracts\HttpClient\HttpClientInterface;
-
-/**
- * @template-extends SingletonProvider<TransportInterface>
- */
-#[Singleton(provides: TransportInterface::class)]
-final readonly class TransportProvider extends SingletonProvider
-{
-    public function __construct(
-        private EventDispatcherInterface $eventDispatcher,
-        private HttpClientInterface $httpClient,
-        private LoggerInterface $logger,
-        private MailerConfiguration $mailerConfiguration,
-    ) {}
-
-    public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): TransportInterface
-    {
-        return Transport::fromDsn(
-            client: $this->httpClient,
-            dispatcher: $this->eventDispatcher,
-            dsn: $this->mailerConfiguration->transportDsn,
-            logger: $this->logger,
-        );
-    }
-}
-- 
GitLab