diff --git a/README.md b/README.md
index 9693c52..afecc45 100644
--- a/README.md
+++ b/README.md
@@ -53,3 +53,128 @@ $evm->addEventListener([Events::postFlush], new DoctrineEventsDispatcher($tracke
 That's it! You're all set!
 Now you can add as many Symfony's listeners as you need to your `$dispatcher`,  
 and you'll be able to react to the domain events raised by your application.
+
+## Outbox pattern
+
+This library also provides support for the [Outbox pattern](https://microservices.io/patterns/data/application-events.html) implementation.  
+The idea behind the implementation is to be able to add entities to an "ongoing" transaction by hooking into Doctrine's `onFlush` event, 
+creating "outbox" entries based on the application's domain events, and safely store them using the same DB transaction.  
+
+In order to enrich your Domain Event and be able to set all the data required by the Outbox entity, 
+you need to create a `Converter` class (by implementing `Dsantang\DomainEventsDoctrine\Outbox\Converter` interface) 
+An example of an outbox event is as follows:
+
+```php
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\Converter;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+
+final class YourOutboxConverter implements Converter
+{
+    public function convert(DomainEvent $domainEvent) : OutboxEntry
+    {
+        return new YourOutboxEntry($domainEvent);
+    }
+}
+
+final class YourOutboxEntry implements OutboxEntry
+{
+    /** @var DomainEvent */
+    private $domainEvent;
+
+    public function __construct(DomainEvent $domainEvent)
+    {
+        $this->domainEvent = $domainEvent;
+    }
+    
+    public function getName() : string
+    {
+        return 'OrderDispatched';
+    }
+
+    public function getAggregateId() : UuidInterface
+    {
+        return Uuid::fromString('d1702762-548b-11e9-8647-d663bd873d93');
+    }
+
+    public function getAggregateType() : string
+    {
+        return 'Order';
+    }
+
+    public function getPayloadType() : string
+    {
+        return 'OrderStructure';
+    }
+
+    public function getMessageKey() : string
+    {
+        return 'd663bd873d93';
+    }
+
+    public function getMessageRoute() : string
+    {
+        return 'aggregate.order';
+    }
+
+    public function getMessageType() : string
+    {
+        return 'OrderCreated';
+    }
+
+    public function getPayload() : string
+    {
+        return json_encode($this->domainEvent)
+    }
+
+    public function getSchemaVersion() : int
+    {
+        return 1;
+    }
+}    
+```
+In order to persist your Outbox entries, you must create a Doctrine Entity class inside your application that extends 
+`Dsantang\DomainEventsDoctrine\Outbox\OutboxMappedSuperclass`.  
+Please note that this approach uses [Doctrine's Inheritance Mapping](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html#mapped-superclasses):  
+
+Here is an example of an Outbox Entity class:   
+```php
+namespace YourNamespace;
+
+use Doctrine\ORM\Mapping as ORM;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxMappedSuperclass;
+
+/**
+ * @ORM\Entity()
+ * @ORM\Table
+ */
+class YourOutboxEntity extends OutboxMappedSuperclass
+{
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    private $someAdditionalField;
+}
+```
+
+And an example of the required configuration as is follows:  
+**Warning:** this solution assumes that you're using `Dsantang\DomainEvents\DomainEvent` in order to raise your domain events.
+
+```php
+use Dsantang\DomainEventsDoctrine\Outbox\MapBased;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxMappedSuperclass;
+
+// Your class must extend OutboxMappedSuperclass
+$yourOutboxEntity = new YourOutboxEntity();
+
+$mapBased = new MapBased($yourOutboxEntity);
+$mapBased->addConverter('YouNamespace\YourDomainEvent', new YourOutboxConverter());
+
+// Always use with OnFlush event
+$evm->addEventListener([Events::onFlush], $mapBased);
+
+```
diff --git a/composer.json b/composer.json
index a9c97e8..84e664f 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,8 @@
     "require": {
         "php": "^7.2",
         "doctrine/orm": "^2.6",
-        "dsantang/domain-events": "^0.3"
+        "dsantang/domain-events": "^0.3",
+        "ramsey/uuid": "^3.8"
     },
     "suggest": {
         "symfony/event-dispatcher" : "To be able to dispatch domain events via a Symfony's EventDispatcherInterface."
diff --git a/composer.lock b/composer.lock
index 2f6e95f..78fb6fb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "9b638c3797b2210c2d4f3c6965b219d1",
+    "content-hash": "21c87779e79f417cb6449f6adc306a59",
     "packages": [
         {
             "name": "doctrine/annotations",
@@ -915,6 +915,133 @@
             ],
             "time": "2019-02-15T14:31:03+00:00"
         },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v9.99.99",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+                "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*",
+                "vimeo/psalm": "^1"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "time": "2018-07-02T15:55:56+00:00"
+        },
+        {
+            "name": "ramsey/uuid",
+            "version": "3.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/uuid.git",
+                "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3",
+                "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/random_compat": "^1.0|^2.0|9.99.99",
+                "php": "^5.4 || ^7.0",
+                "symfony/polyfill-ctype": "^1.8"
+            },
+            "replace": {
+                "rhumsaa/uuid": "self.version"
+            },
+            "require-dev": {
+                "codeception/aspect-mock": "^1.0 | ~2.0.0",
+                "doctrine/annotations": "~1.2.0",
+                "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0",
+                "ircmaxell/random-lib": "^1.1",
+                "jakub-onderka/php-parallel-lint": "^0.9.0",
+                "mockery/mockery": "^0.9.9",
+                "moontoast/math": "^1.1",
+                "php-mock/php-mock-phpunit": "^0.3|^1.1",
+                "phpunit/phpunit": "^4.7|^5.0|^6.5",
+                "squizlabs/php_codesniffer": "^2.3"
+            },
+            "suggest": {
+                "ext-ctype": "Provides support for PHP Ctype functions",
+                "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
+                "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
+                "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+                "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
+                "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
+                "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Ramsey\\Uuid\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marijn Huizendveld",
+                    "email": "marijn.huizendveld@gmail.com"
+                },
+                {
+                    "name": "Thibaud Fabre",
+                    "email": "thibaud@aztech.io"
+                },
+                {
+                    "name": "Ben Ramsey",
+                    "email": "ben@benramsey.com",
+                    "homepage": "https://benramsey.com"
+                }
+            ],
+            "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
+            "homepage": "https://github.com/ramsey/uuid",
+            "keywords": [
+                "guid",
+                "identifier",
+                "uuid"
+            ],
+            "time": "2018-07-19T23:38:55+00:00"
+        },
         {
             "name": "symfony/console",
             "version": "v4.2.3",
@@ -1055,6 +1182,64 @@
             ],
             "time": "2018-12-05T08:06:11+00:00"
         },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                },
+                {
+                    "name": "Gert de Pagter",
+                    "email": "backendtea@gmail.com"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2018-08-06T14:22:27+00:00"
+        },
         {
             "name": "symfony/polyfill-mbstring",
             "version": "v1.10.0",
@@ -1598,7 +1783,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.",
+            "description": "? Nette Bootstrap: the simple way to configure and bootstrap your Nette application.",
             "homepage": "https://nette.org",
             "keywords": [
                 "bootstrapping",
@@ -1663,7 +1848,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.",
+            "description": "? Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.",
             "homepage": "https://nette.org",
             "keywords": [
                 "compiled",
@@ -1728,7 +1913,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🔍 Nette Finder: find files and directories with an intuitive API.",
+            "description": "? Nette Finder: find files and directories with an intuitive API.",
             "homepage": "https://nette.org",
             "keywords": [
                 "filesystem",
@@ -1788,7 +1973,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🍸 Nette NEON: encodes and decodes NEON file format.",
+            "description": "? Nette NEON: encodes and decodes NEON file format.",
             "homepage": "http://ne-on.org",
             "keywords": [
                 "export",
@@ -1851,7 +2036,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.",
+            "description": "? Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.",
             "homepage": "https://nette.org",
             "keywords": [
                 "code",
@@ -1915,7 +2100,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
+            "description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
             "homepage": "https://nette.org",
             "keywords": [
                 "autoload",
@@ -1988,7 +2173,7 @@
                     "homepage": "https://nette.org/contributors"
                 }
             ],
-            "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "description": "? Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
             "homepage": "https://nette.org",
             "keywords": [
                 "array",
@@ -4060,64 +4245,6 @@
             "homepage": "https://symfony.com",
             "time": "2019-01-16T20:35:37+00:00"
         },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.10.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
-                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.3"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.9-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                },
-                {
-                    "name": "Gert de Pagter",
-                    "email": "BackEndTea@gmail.com"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "time": "2018-08-06T14:22:27+00:00"
-        },
         {
             "name": "symfony/process",
             "version": "v4.2.2",
diff --git a/src/Outbox/Converter.php b/src/Outbox/Converter.php
new file mode 100644
index 0000000..e1f619b
--- /dev/null
+++ b/src/Outbox/Converter.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Outbox;
+
+use Dsantang\DomainEvents\DomainEvent;
+
+interface Converter
+{
+    public function convert(DomainEvent $domainEvent) : OutboxEntry;
+}
diff --git a/src/Outbox/EventsHandler.php b/src/Outbox/EventsHandler.php
new file mode 100644
index 0000000..3d24926
--- /dev/null
+++ b/src/Outbox/EventsHandler.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Outbox;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Event\OnFlushEventArgs;
+use Doctrine\ORM\UnitOfWork;
+use Dsantang\DomainEvents\Counter;
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEvents\EventAware;
+use function array_filter;
+use function array_merge;
+use function count;
+use function ksort;
+
+abstract class EventsHandler
+{
+    /** @var OutboxMappedSuperclass */
+    protected $outboxMappedSuperclass;
+
+    public function __construct(OutboxMappedSuperclass $outboxMappedSuperclass)
+    {
+        $this->outboxMappedSuperclass = $outboxMappedSuperclass;
+    }
+
+    /**
+     * @return OutboxEntry[]
+     *
+     * @var DomainEvent[] $domainEvents
+     */
+    abstract protected function convert(DomainEvent ...$domainEvents) : array;
+
+    public function onFlush(OnFlushEventArgs $eventArgs) : void
+    {
+        $domainEvents = $this->getDomainEvents($eventArgs);
+
+        $outboxEvents = $this->convert(...$domainEvents);
+
+        $this->persist($eventArgs->getEntityManager(), ...$outboxEvents);
+    }
+
+    /**
+     * @return DomainEvent[]
+     */
+    protected function getDomainEvents(OnFlushEventArgs $eventArgs) : array
+    {
+        $unitOfWork = $eventArgs->getEntityManager()
+            ->getUnitOfWork();
+
+        $events = [];
+
+        foreach (self::getEventAwareEntities($unitOfWork) as $entity) {
+            $events += $entity->expelRecordedEvents();
+        }
+
+        ksort($events);
+
+        Counter::reset();
+
+        return $events;
+    }
+
+    protected function persist(EntityManagerInterface $entityManager, OutboxEntry ...$outboxEntries) : void
+    {
+        if (count($outboxEntries) <= 0) {
+            return;
+        }
+
+        foreach ($outboxEntries as $outboxEntry) {
+            $entity = $this->outboxMappedSuperclass->fromOutboxEntry($outboxEntry);
+            $entityManager->persist($entity);
+        }
+
+        $entityManager->getUnitOfWork()->computeChangeSets();
+    }
+
+    /**
+     * @return EventAware[]
+     */
+    private static function getEventAwareEntities(UnitOfWork $unitOfWork) : array
+    {
+        $entities = array_merge(
+            $unitOfWork->getScheduledEntityInsertions(),
+            $unitOfWork->getScheduledEntityUpdates(),
+            $unitOfWork->getScheduledEntityDeletions()
+        );
+
+        return array_filter($entities, static function ($entity) {
+            return $entity instanceof EventAware;
+        });
+    }
+}
diff --git a/src/Outbox/MapBased.php b/src/Outbox/MapBased.php
new file mode 100644
index 0000000..62f0b03
--- /dev/null
+++ b/src/Outbox/MapBased.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Outbox;
+
+use Dsantang\DomainEvents\DomainEvent;
+use InvalidArgumentException;
+use function class_exists;
+use function get_class;
+use function sprintf;
+
+final class MapBased extends EventsHandler
+{
+    /** @var Converter[] */
+    private $conversionMap;
+
+    public function addConverter(string $domainEventClass, Converter $converter) : void
+    {
+        if (! class_exists($domainEventClass)) {
+            throw new InvalidArgumentException(
+                sprintf('Domain Event \"%s\" does not exist', $domainEventClass)
+            );
+        }
+
+        $this->conversionMap[$domainEventClass] = $converter;
+    }
+
+    /**
+     * @return OutboxEntry[]
+     *
+     * @var DomainEvent[] $domainEvents
+     */
+    public function convert(DomainEvent ...$domainEvents) : array
+    {
+        $outboxEntries = [];
+
+        foreach ($domainEvents as $domainEvent) {
+            if (! isset($this->conversionMap[get_class($domainEvent)])) {
+                continue;
+            }
+
+            $converter = $this->conversionMap[get_class($domainEvent)];
+
+            $outboxEntries[] = $converter->convert($domainEvent);
+        }
+
+        return $outboxEntries;
+    }
+}
diff --git a/src/Outbox/OutboxEntry.php b/src/Outbox/OutboxEntry.php
new file mode 100644
index 0000000..f19a67f
--- /dev/null
+++ b/src/Outbox/OutboxEntry.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Outbox;
+
+use Ramsey\Uuid\UuidInterface;
+
+interface OutboxEntry
+{
+    public function getMessageKey() : string;
+
+    public function getMessageRoute() : string;
+
+    public function getMessageType() : string;
+
+    public function getAggregateId() : UuidInterface;
+
+    public function getAggregateType() : string;
+
+    public function getPayloadType() : string;
+
+    public function getPayload() : string;
+
+    public function getSchemaVersion() : int;
+}
diff --git a/src/Outbox/OutboxMappedSuperclass.php b/src/Outbox/OutboxMappedSuperclass.php
new file mode 100644
index 0000000..a7893b3
--- /dev/null
+++ b/src/Outbox/OutboxMappedSuperclass.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Outbox;
+
+use DateTimeImmutable;
+use Doctrine\ORM\Mapping as ORM;
+use Doctrine\ORM\Mapping\MappedSuperclass;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+
+/**
+ * @MappedSuperclass
+ */
+abstract class OutboxMappedSuperclass
+{
+    /**
+     * @ORM\Id
+     * @ORM\Column(type="uuid")
+     *
+     * @var UuidInterface
+     */
+    protected $id;
+
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    protected $messageKey;
+
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    protected $messageRoute;
+
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    protected $messageType;
+
+    /**
+     * @ORM\Column(type="uuid")
+     *
+     * @var UuidInterface
+     */
+    protected $aggregateId;
+
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    protected $aggregateType;
+
+    /**
+     * @ORM\Column(type="string")
+     *
+     * @var string
+     */
+    protected $payloadType;
+
+    /**
+     * @ORM\Column(type="json")
+     *
+     * @var string
+     */
+    protected $payload;
+
+    /**
+     * @ORM\Column(type="integer")
+     *
+     * @var int
+     */
+    protected $schemaVersion;
+
+    /**
+     * @ORM\Column(type="utc_datetime_immutable")
+     *
+     * @var DateTimeImmutable
+     */
+    protected $createdAt;
+
+    public function fromOutboxEntry(OutboxEntry $outboxEntry) : OutboxMappedSuperclass
+    {
+        $outbox = clone $this;
+
+        $outbox->id            = Uuid::uuid4();
+        $outbox->messageKey    = $outboxEntry->getMessageKey();
+        $outbox->messageRoute  = $outboxEntry->getMessageRoute();
+        $outbox->messageType   = $outboxEntry->getMessageType();
+        $outbox->aggregateId   = $outboxEntry->getAggregateId();
+        $outbox->aggregateType = $outboxEntry->getAggregateType();
+        $outbox->payloadType   = $outboxEntry->getPayloadType();
+        $outbox->payload       = $outboxEntry->getPayload();
+        $outbox->schemaVersion = $outboxEntry->getSchemaVersion();
+        $outbox->createdAt     = new DateTimeImmutable();
+
+        return $outbox;
+    }
+}
diff --git a/tests/Integration/MapBasedEventsHandlerTest.php b/tests/Integration/MapBasedEventsHandlerTest.php
new file mode 100644
index 0000000..d4956bf
--- /dev/null
+++ b/tests/Integration/MapBasedEventsHandlerTest.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Integration;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\UnitOfWork;
+use Dsantang\DomainEventsDoctrine\Outbox\MapBased;
+use Dsantang\DomainEventsDoctrine\Tests\OutboxSubClass;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\EventArgsProvider;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters\FirstOutboxConverter;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\FirstDomainEvent;
+use PHPUnit\Framework\TestCase;
+
+final class MapBasedEventsHandlerTest extends TestCase
+{
+    use EventArgsProvider;
+
+    /** @var MapBased */
+    private $mapBasedEventsHandler;
+
+    /**
+     * @before
+     */
+    public function setUpDependencies() : void
+    {
+        $this->mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        $this->entityManager = $this->createMock(EntityManagerInterface::class);
+        $this->unitOfWork    = $this->createMock(UnitOfWork::class);
+    }
+
+    public function testOutboxHappyPathWorkflowOnFlushEvent() : void
+    {
+        $this->mapBasedEventsHandler->addConverter(
+            FirstDomainEvent::class,
+            new FirstOutboxConverter()
+        );
+
+        $eventArgs = $this->getEventArgs();
+
+        $this->entityManager->expects(self::once())->method('persist');
+        $this->unitOfWork->expects(self::once())->method('computeChangeSets');
+
+        $this->mapBasedEventsHandler->onFlush($eventArgs);
+    }
+}
diff --git a/tests/OutboxSubClass.php b/tests/OutboxSubClass.php
new file mode 100644
index 0000000..e3d1100
--- /dev/null
+++ b/tests/OutboxSubClass.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests;
+
+use Doctrine\ORM\Mapping as ORM;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxMappedSuperclass;
+
+/**
+ * @ORM\Entity()
+ * @ORM\Table
+ */
+class OutboxSubClass extends OutboxMappedSuperclass
+{
+    /**
+     * @ORM\Column(type="string", name="field_1", nullable=true)
+     *
+     * @var string
+     */
+    private $field1;
+
+    /**
+     * @ORM\Column(type="string", name="field_2", nullable=true)
+     *
+     * @var string
+     */
+    private $field2;
+
+    public function getField1() : string
+    {
+        return $this->field1;
+    }
+
+    public function getField2() : string
+    {
+        return $this->field2;
+    }
+}
diff --git a/tests/Unit/Outbox/EventArgsProvider.php b/tests/Unit/Outbox/EventArgsProvider.php
new file mode 100644
index 0000000..67ef4eb
--- /dev/null
+++ b/tests/Unit/Outbox/EventArgsProvider.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox;
+
+use Doctrine\ORM\Event\OnFlushEventArgs;
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEvents\EventAware;
+use Dsantang\DomainEvents\Registry\OrderedEventRegistry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\FirstDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\SecondDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\ThirdDomainEvent;
+use PHPUnit\Framework\MockObject\MockObject;
+
+trait EventArgsProvider
+{
+    /** @var MockObject entityManager */
+    private $entityManager;
+
+    /** @var MockObject unitOfWork */
+    private $unitOfWork;
+
+    /**
+     * @return OnFlushEventArgs|MockObject
+     */
+    private function getEventArgs(bool $withEvents = true) : OnFlushEventArgs
+    {
+        $insertions = $updates = $deletions = [];
+
+        if ($withEvents) {
+            $entity1 = new class() implements EventAware {
+                use OrderedEventRegistry;
+
+                public function trigger(DomainEvent $event) : void
+                {
+                    $this->triggeredA($event);
+                }
+            };
+
+            $entity2 = new class() implements EventAware {
+                use OrderedEventRegistry;
+
+                public function trigger(DomainEvent $event) : void
+                {
+                    $this->triggeredA($event);
+                }
+            };
+
+            $entity3 = new class() implements EventAware {
+                use OrderedEventRegistry;
+
+                public function trigger(DomainEvent $event) : void
+                {
+                    $this->triggeredA($event);
+                }
+            };
+
+            $domainEvent1 = new FirstDomainEvent();
+            $domainEvent2 = new SecondDomainEvent();
+            $domainEvent3 = new ThirdDomainEvent();
+
+            $entity1->trigger($domainEvent1);
+            $entity2->trigger($domainEvent2);
+            $entity3->trigger($domainEvent3);
+
+            $updates   = [$entity3];
+            $deletions = [$entity2, $entity1];
+        }
+
+        $this->unitOfWork->expects(self::any())->method('getScheduledEntityInsertions')->willReturn($insertions);
+        $this->unitOfWork->expects(self::any())->method('getScheduledEntityUpdates')->willReturn($updates);
+        $this->unitOfWork->expects(self::any())->method('getScheduledEntityDeletions')->willReturn($deletions);
+
+        $this->entityManager->expects(self::any())->method('getUnitOfWork')->willReturn($this->unitOfWork);
+
+        $eventArgs = $this->createMock(OnFlushEventArgs::class);
+        $eventArgs->expects(self::any())->method('getEntityManager')->willReturn($this->entityManager);
+
+        return $eventArgs;
+    }
+}
diff --git a/tests/Unit/Outbox/MapBasedTest.php b/tests/Unit/Outbox/MapBasedTest.php
new file mode 100644
index 0000000..d460f18
--- /dev/null
+++ b/tests/Unit/Outbox/MapBasedTest.php
@@ -0,0 +1,145 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\UnitOfWork;
+use Dsantang\DomainEventsDoctrine\Outbox\Converter;
+use Dsantang\DomainEventsDoctrine\Outbox\MapBased;
+use Dsantang\DomainEventsDoctrine\Tests\OutboxSubClass;
+use Dsantang\DomainEventsDoctrine\Tests\RandomDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters\FirstOutboxConverter;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters\SecondOutboxConverter;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters\ThirdOutboxConverter;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\FirstDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\SecondDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\ThirdDomainEvent;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+use function array_pop;
+
+final class MapBasedTest extends TestCase
+{
+    use EventArgsProvider;
+
+    /** @var Converter[] */
+    private $conversionMap;
+
+    /**
+     * @before
+     */
+    public function setUpDependencies() : void
+    {
+        $this->conversionMap = [
+            FirstDomainEvent::class  => new FirstOutboxConverter(),
+            SecondDomainEvent::class => new SecondOutboxConverter(),
+            ThirdDomainEvent::class  => new ThirdOutboxConverter(),
+        ];
+
+        $this->entityManager = $this->createMock(EntityManagerInterface::class);
+        $this->unitOfWork    = $this->createMock(UnitOfWork::class);
+    }
+
+    public function testDealWithInvalidKeyConversionMap() : void
+    {
+        $this->expectException(InvalidArgumentException::class);
+
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        $mapBasedEventsHandler->addConverter(
+            'Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\NonExistentDomainEvent',
+            new FirstOutboxConverter()
+        );
+    }
+
+    public function testConvert() : void
+    {
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        foreach ($this->conversionMap as $domainEventClassName => $converter) {
+            $mapBasedEventsHandler->addConverter($domainEventClassName, $converter);
+        }
+
+        $domainEvents = [new ThirdDomainEvent(), new FirstDomainEvent(), new SecondDomainEvent()];
+
+        $returnedOutboxEvents = $mapBasedEventsHandler->convert(...$domainEvents);
+
+        $expectedOutboxEvents = [
+            (new ThirdOutboxConverter())->convert(new ThirdDomainEvent()),
+            (new FirstOutboxConverter())->convert(new FirstDomainEvent()),
+            (new SecondOutboxConverter())->convert(new SecondDomainEvent()),
+        ];
+
+        self::assertEquals($expectedOutboxEvents, $returnedOutboxEvents);
+    }
+
+    public function testConvertWithANonOutboxEntryDomainEvent() : void
+    {
+        array_pop($this->conversionMap);
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        foreach ($this->conversionMap as $domainEventClassName => $converter) {
+            $mapBasedEventsHandler->addConverter($domainEventClassName, $converter);
+        }
+
+        $domainEvents = [new FirstDomainEvent(), new SecondDomainEvent(), new ThirdDomainEvent()];
+
+        $returnedOutboxEvents = $mapBasedEventsHandler->convert(...$domainEvents);
+
+        $expectedOutboxEvents = [
+            (new FirstOutboxConverter())->convert(new FirstDomainEvent()),
+            (new SecondOutboxConverter())->convert(new SecondDomainEvent()),
+        ];
+
+        self::assertEquals($expectedOutboxEvents, $returnedOutboxEvents);
+    }
+
+    public function testConvertANonOutboxRelatedDomainEvent() : void
+    {
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        foreach ($this->conversionMap as $domainEventClassName => $converter) {
+            $mapBasedEventsHandler->addConverter($domainEventClassName, $converter);
+        }
+
+        $domainEvents = [new RandomDomainEvent(), new SecondDomainEvent()];
+
+        $returnedOutboxEvents = $mapBasedEventsHandler->convert(...$domainEvents);
+
+        $expectedOutboxEvents = [(new SecondOutboxConverter())->convert(new SecondDomainEvent())];
+
+        self::assertEquals($expectedOutboxEvents, $returnedOutboxEvents);
+    }
+
+    public function testOnFlushWithDomainEvents() : void
+    {
+        $eventArgs = $this->getEventArgs();
+
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        foreach ($this->conversionMap as $domainEventClassName => $converter) {
+            $mapBasedEventsHandler->addConverter($domainEventClassName, $converter);
+        }
+
+        $this->entityManager->expects(self::exactly(3))->method('persist');
+
+        $mapBasedEventsHandler->onFlush($eventArgs);
+    }
+
+    public function testOnFlushWithNoDomainEvents() : void
+    {
+        $eventArgs = $this->getEventArgs(false);
+
+        $mapBasedEventsHandler = new MapBased(new OutboxSubClass());
+
+        foreach ($this->conversionMap as $domainEventClassName => $converter) {
+            $mapBasedEventsHandler->addConverter($domainEventClassName, $converter);
+        }
+
+        $this->unitOfWork->expects(self::never())->method('computeChangeSets');
+
+        $mapBasedEventsHandler->onFlush($eventArgs);
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/Converters/FirstOutboxConverter.php b/tests/Unit/Outbox/Stub/Converters/FirstOutboxConverter.php
new file mode 100644
index 0000000..a67896d
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/Converters/FirstOutboxConverter.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\Converter;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\FirstOutboxEntry;
+
+final class FirstOutboxConverter implements Converter
+{
+    public function convert(DomainEvent $domainEvent) : OutboxEntry
+    {
+        return new FirstOutboxEntry($domainEvent);
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/Converters/SecondOutboxConverter.php b/tests/Unit/Outbox/Stub/Converters/SecondOutboxConverter.php
new file mode 100644
index 0000000..ac63f68
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/Converters/SecondOutboxConverter.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\Converter;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\SecondOutboxEntry;
+
+final class SecondOutboxConverter implements Converter
+{
+    public function convert(DomainEvent $domainEvent) : OutboxEntry
+    {
+        return new SecondOutboxEntry($domainEvent);
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/Converters/ThirdOutboxConverter.php b/tests/Unit/Outbox/Stub/Converters/ThirdOutboxConverter.php
new file mode 100644
index 0000000..4bcf94a
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/Converters/ThirdOutboxConverter.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\Converters;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\Converter;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\ThirdOutboxEntry;
+
+final class ThirdOutboxConverter implements Converter
+{
+    public function convert(DomainEvent $domainEvent) : OutboxEntry
+    {
+        return new ThirdOutboxEntry($domainEvent);
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/DomainEvents/FirstDomainEvent.php b/tests/Unit/Outbox/Stub/DomainEvents/FirstDomainEvent.php
new file mode 100644
index 0000000..45e1227
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/DomainEvents/FirstDomainEvent.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents;
+
+use Dsantang\DomainEvents\DomainEvent;
+
+final class FirstDomainEvent implements DomainEvent
+{
+    public function getName() : string
+    {
+        return 'first';
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/DomainEvents/SecondDomainEvent.php b/tests/Unit/Outbox/Stub/DomainEvents/SecondDomainEvent.php
new file mode 100644
index 0000000..444127f
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/DomainEvents/SecondDomainEvent.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents;
+
+use Dsantang\DomainEvents\DomainEvent;
+
+final class SecondDomainEvent implements DomainEvent
+{
+    public function getName() : string
+    {
+        return 'second';
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/DomainEvents/ThirdDomainEvent.php b/tests/Unit/Outbox/Stub/DomainEvents/ThirdDomainEvent.php
new file mode 100644
index 0000000..56000c0
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/DomainEvents/ThirdDomainEvent.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents;
+
+use Dsantang\DomainEvents\DomainEvent;
+
+final class ThirdDomainEvent implements DomainEvent
+{
+    public function getName() : string
+    {
+        return 'third';
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/OutboxEntries/FirstOutboxEntry.php b/tests/Unit/Outbox/Stub/OutboxEntries/FirstOutboxEntry.php
new file mode 100644
index 0000000..2b1c726
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/OutboxEntries/FirstOutboxEntry.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+
+final class FirstOutboxEntry implements OutboxEntry
+{
+    /** @var DomainEvent */
+    private $domainEvent;
+
+    public function __construct(DomainEvent $domainEvent)
+    {
+        $this->domainEvent = $domainEvent;
+    }
+
+    public function getAggregateId() : UuidInterface
+    {
+        return Uuid::fromString('d1702762-548b-11e9-8647-d663bd873d93');
+    }
+
+    public function getAggregateType() : string
+    {
+        return 'Order';
+    }
+
+    public function getPayloadType() : string
+    {
+        return 'OrderStructure';
+    }
+
+    public function getMessageKey() : string
+    {
+        return 'd663bd873d93';
+    }
+
+    public function getMessageRoute() : string
+    {
+        return 'aggregate.order';
+    }
+
+    public function getMessageType() : string
+    {
+        return $this->domainEvent->getName();
+    }
+
+    public function getPayload() : string
+    {
+        return '{"foo":"bar"}';
+    }
+
+    public function getSchemaVersion() : int
+    {
+        return 5;
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/OutboxEntries/SecondOutboxEntry.php b/tests/Unit/Outbox/Stub/OutboxEntries/SecondOutboxEntry.php
new file mode 100644
index 0000000..a529908
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/OutboxEntries/SecondOutboxEntry.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+
+final class SecondOutboxEntry implements OutboxEntry
+{
+    /** @var DomainEvent */
+    private $domainEvent;
+
+    public function __construct(DomainEvent $domainEvent)
+    {
+        $this->domainEvent = $domainEvent;
+    }
+
+    public function getName() : string
+    {
+        return 'CartCreated';
+    }
+
+    public function getAggregateId() : UuidInterface
+    {
+        return Uuid::fromString('cece22ea-57ae-11e9-8647-d663bd873d93');
+    }
+
+    public function getAggregateType() : string
+    {
+        return 'Cart';
+    }
+
+    public function getPayloadType() : string
+    {
+        return 'CartType';
+    }
+
+    public function getMessageKey() : string
+    {
+        return 'af422e7a';
+    }
+
+    public function getMessageRoute() : string
+    {
+        return 'snapshot.cart';
+    }
+
+    public function getMessageType() : string
+    {
+        return $this->domainEvent->getName();
+    }
+
+    public function getPayload() : string
+    {
+        return '{"foo":"bar"}';
+    }
+
+    public function getSchemaVersion() : int
+    {
+        return 1;
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/OutboxEntries/ThirdOutboxEntry.php b/tests/Unit/Outbox/Stub/OutboxEntries/ThirdOutboxEntry.php
new file mode 100644
index 0000000..46538ed
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/OutboxEntries/ThirdOutboxEntry.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries;
+
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+use Ramsey\Uuid\Uuid;
+use Ramsey\Uuid\UuidInterface;
+
+final class ThirdOutboxEntry implements OutboxEntry
+{
+    /** @var DomainEvent */
+    private $domainEvent;
+
+    public function __construct(DomainEvent $domainEvent)
+    {
+        $this->domainEvent = $domainEvent;
+    }
+
+    public function getName() : string
+    {
+        return 'CustomerDeleted';
+    }
+
+    public function getAggregateId() : UuidInterface
+    {
+        return Uuid::fromString('49ca1f44-56ec-11e9-8647-d663bd873d93');
+    }
+
+    public function getAggregateType() : string
+    {
+        return 'CustomerType';
+    }
+
+    public function getPayloadType() : string
+    {
+        return 'CustomerDetails';
+    }
+
+    public function getMessageKey() : string
+    {
+        return 'ba70d882';
+    }
+
+    public function getMessageRoute() : string
+    {
+        return 'event.customer';
+    }
+
+    public function getMessageType() : string
+    {
+        return $this->domainEvent->getName();
+    }
+
+    public function getPayload() : string
+    {
+        return '{"foo":"bar"}';
+    }
+
+    public function getSchemaVersion() : int
+    {
+        return 2;
+    }
+}
diff --git a/tests/Unit/Outbox/Stub/StubMapBased.php b/tests/Unit/Outbox/Stub/StubMapBased.php
new file mode 100644
index 0000000..6bff4f2
--- /dev/null
+++ b/tests/Unit/Outbox/Stub/StubMapBased.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Event\OnFlushEventArgs;
+use Dsantang\DomainEvents\DomainEvent;
+use Dsantang\DomainEventsDoctrine\Outbox\EventsHandler;
+use Dsantang\DomainEventsDoctrine\Outbox\OutboxEntry;
+
+final class StubMapBased extends EventsHandler
+{
+    /**
+     * @return DomainEvent[] array
+     */
+    public function getDomainEvents(OnFlushEventArgs $eventArgs) : array
+    {
+        return parent::getDomainEvents($eventArgs);
+    }
+
+    public function persist(EntityManagerInterface $entityManager, OutboxEntry ...$outboxEntries) : void
+    {
+        parent::persist($entityManager, ...$outboxEntries);
+    }
+
+    /**
+     * @return OutboxEntry[]
+     *
+     * @var DomainEvent[] $domainEvents
+     */
+    protected function convert(DomainEvent ...$domainEvents) : array
+    {
+        return [];
+    }
+}
diff --git a/tests/Unit/Outbox/StubMapBasedTest.php b/tests/Unit/Outbox/StubMapBasedTest.php
new file mode 100644
index 0000000..89494e9
--- /dev/null
+++ b/tests/Unit/Outbox/StubMapBasedTest.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\UnitOfWork;
+use Dsantang\DomainEvents\Counter;
+use Dsantang\DomainEventsDoctrine\Tests\OutboxSubClass;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\FirstDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\SecondDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\DomainEvents\ThirdDomainEvent;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\FirstOutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\SecondOutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\OutboxEntries\ThirdOutboxEntry;
+use Dsantang\DomainEventsDoctrine\Tests\Unit\Outbox\Stub\StubMapBased;
+use PHPUnit\Framework\TestCase;
+use function array_values;
+
+final class StubMapBasedTest extends TestCase
+{
+    use EventArgsProvider;
+
+    /**
+     * @before
+     */
+    public function setUpDependencies() : void
+    {
+        $this->entityManager = $this->createMock(EntityManagerInterface::class);
+        $this->unitOfWork    = $this->createMock(UnitOfWork::class);
+    }
+
+    public function testGetDomainsEvents() : void
+    {
+        Counter::reset();
+
+        $eventArgs = $this->getEventArgs();
+
+        self::assertEquals(3, Counter::getNext());
+
+        $mapBasedEventsHandler = new StubMapBased(new OutboxSubClass());
+
+        $eventsResult = $mapBasedEventsHandler->getDomainEvents($eventArgs);
+
+        self::assertEquals(0, Counter::getNext());
+
+        $eventsExpected = [new FirstDomainEvent(), new SecondDomainEvent(), new ThirdDomainEvent()];
+
+        self::assertEquals(array_values($eventsResult), array_values($eventsExpected));
+    }
+
+    /**
+     * @return mixed[] array
+     */
+    public function persistDataProvider() : array
+    {
+        return [
+            [[], 0, 0],
+            [[new FirstOutboxEntry(new FirstDomainEvent())], 1, 1],
+            [
+                [
+                    new ThirdOutboxEntry(new ThirdDomainEvent()),
+                    new FirstOutboxEntry(new FirstDomainEvent()),
+                    new SecondOutboxEntry(new SecondDomainEvent()),
+                ], 3,
+                1,
+            ],
+        ];
+    }
+
+    /**
+     * @param mixed[] $outboxEvents
+     *
+     * @dataProvider persistDataProvider
+     */
+    public function testPersist(array $outboxEvents, int $persistCalls, int $computeChangeSetsCalls) : void
+    {
+        $eventArgs = $this->getEventArgs();
+
+        $mapBasedEventsHandler = new StubMapBased(new OutboxSubClass());
+
+        $this->entityManager->expects(self::exactly($persistCalls))->method('persist');
+
+        $this->unitOfWork->expects(self::exactly($computeChangeSetsCalls))->method('computeChangeSets');
+
+        $mapBasedEventsHandler->persist($eventArgs->getEntityManager(), ...$outboxEvents);
+    }
+}