From 63f6380c192498cbd0b1fa8f492c1e09d813b6b1 Mon Sep 17 00:00:00 2001 From: martijn van haagen Date: Thu, 31 Oct 2024 15:29:41 +0100 Subject: [PATCH 1/3] [FEATURE][SPC-4644] Started rework --- Model/Emailcatcher.php | 25 +++++ .../Framework/Mail/TransportInterface.php | 67 ++++++++------ .../Mail/TransportInterfacePlugin.php | 92 +++++++++++++++++++ etc/adminhtml/system.xml | 30 ++++++ etc/config.xml | 7 ++ etc/di.xml | 5 +- 6 files changed, 194 insertions(+), 32 deletions(-) create mode 100644 Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php diff --git a/Model/Emailcatcher.php b/Model/Emailcatcher.php index 9c1d1df..08ad45a 100644 --- a/Model/Emailcatcher.php +++ b/Model/Emailcatcher.php @@ -13,9 +13,18 @@ use Magento\Framework\Model\ResourceModel\AbstractResource; use Magento\Framework\Registry; use Experius\EmailCatcher\Registry\CurrentTemplate; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; class Emailcatcher extends AbstractModel { + const CONFIG_PATH_EMAIL_CATCHER_ENABLED = 'emailcatcher/general/enabled'; + const CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ADMIN_ALLOWED_ENABLED = 'emailcatcher/development/enabled'; + const CONFIG_PATH_WHITELIST_APPLY_WHITELIST = 'emailcatcher/whitelist/apply_whitelist'; + const CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST = 'emailcatcher/blacklist/apply_whitelist'; + const CONFIG_PATH_TEMPLATE_WHITELIST = 'emailcatcher/whitelist/email_templates'; + const CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ALLOWED_ADDRESSES = 'emailcatcher/development/allow_email_addresses'; + /** * @var string */ @@ -38,6 +47,7 @@ public function __construct( Context $context, Registry $registry, protected CurrentTemplate $currentTemplate, + protected ScopeConfigInterface $scopeConfig, AbstractResource $resource = null, AbstractDb $resourceCollection = null, array $data = [] @@ -120,4 +130,19 @@ public function imapUtf8($string) { return (function_exists('imap_utf8') && is_string($string)) ? imap_utf8($string) : $string; } + + public function blackListEnabled(): bool + { + return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST, ScopeInterface::SCOPE_STORE); + } + + public function whitelistEnabled(): bool + { + return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_WHITELIST_APPLY_WHITELIST, ScopeInterface::SCOPE_STORE); + } + + public function emailCatcherEnabled(): bool + { + return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_EMAIL_CATCHER_ENABLED, ScopeInterface::SCOPE_STORE); + } } diff --git a/Plugin/Magento/Framework/Mail/TransportInterface.php b/Plugin/Magento/Framework/Mail/TransportInterface.php index bac135c..08b2e03 100644 --- a/Plugin/Magento/Framework/Mail/TransportInterface.php +++ b/Plugin/Magento/Framework/Mail/TransportInterface.php @@ -11,24 +11,22 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use \Magento\Store\Model\ScopeInterface; use Experius\EmailCatcher\Registry\CurrentTemplate; +use Experius\EmailCatcher\Model\Emailcatcher; class TransportInterface { - const CONFIG_PATH_EMAIL_CATCHER_ENABLED = 'emailcatcher/general/enabled'; - const CONFIG_PATH_WHITELIST_APPLY_WHITELIST = 'emailcatcher/whitelist/apply_whitelist'; - const CONFIG_PATH_TEMPLATE_WHITELIST = 'emailcatcher/whitelist/email_templates'; - /** * @param ScopeConfigInterface $scopeConfig - * @param EmailcatcherFactory $emailCatcher + * @param EmailcatcherFactory $emailCatcherFactory * @param CurrentTemplate $currentTemplate + * @param Emailcatcher $emailcatcher */ public function __construct( private ScopeConfigInterface $scopeConfig, - private EmailcatcherFactory $emailCatcher, - private CurrentTemplate $currentTemplate - ) { - } + private EmailcatcherFactory $emailCatcherFactory, + private CurrentTemplate $currentTemplate, + private Emailcatcher $emailcatcher + ) {} /** * Around sendMessage plugin @@ -42,18 +40,23 @@ public function aroundSendMessage( \Magento\Framework\Mail\TransportInterface $subject, \Closure $proceed ) { - if (!$this->scopeConfig->isSetFlag(self::CONFIG_PATH_EMAIL_CATCHER_ENABLED, ScopeInterface::SCOPE_STORE)) { + if (!$this->emailcatcher->emailCatcherEnabled()) { return $proceed(); } + if ($this->emailcatcher->blackListEnabled() && in_array($this->getToEmailAddress($subject), $this->getBlacklistEmailAddresses())) { + $subject->getMessage()->setSubject('Prevent Being Sent'); + $this->saveMessage($subject); + + return; + } + $this->saveMessage($subject); - // Proceed if whitelist feature is not enabled - if (!$this->scopeConfig->isSetFlag(self::CONFIG_PATH_WHITELIST_APPLY_WHITELIST, ScopeInterface::SCOPE_STORE)) { + if (!$this->emailcatcher->whitelistEnabled()) { return $proceed(); } - // Check if template is whitelisted $currentTemplate = $this->currentTemplate->get(); if (!empty($this->getTemplateWhitelist())) { if (in_array($currentTemplate, $this->getTemplateWhitelist())) { @@ -63,38 +66,42 @@ public function aroundSendMessage( } /** - * Save message - * * @param $subject * @return void - * @throws \ReflectionException */ - private function saveMessage($subject) + private function saveMessage($subject): void { - // For >= 2.2 - if (method_exists($subject, 'getMessage')) { - $this->emailCatcher->create()->saveMessage($subject->getMessage()); - } else { - //For < 2.2 - $reflection = new \ReflectionClass($subject); - $property = $reflection->getProperty('_message'); - $property->setAccessible(true); - $this->emailCatcher->create()->saveMessage($property->getValue($subject)); - } + $this->emailCatcherFactory->create()->saveMessage($subject->getMessage()); } /** * Get whitelisted templates - * * @return array */ - private function getTemplateWhitelist() + private function getTemplateWhitelist(): array { $templates = $this->scopeConfig->getValue( - self::CONFIG_PATH_TEMPLATE_WHITELIST, + Emailcatcher::CONFIG_PATH_TEMPLATE_WHITELIST, ScopeInterface::SCOPE_STORE ); return $templates ? explode(',', $templates) : []; } + + /** + * @return array + */ + protected function getBlacklistEmailAddresses() : array + { + return $this->scopeConfig->getValue('prevent_sending_email/blacklist/block_email_addresses'); + } + + /** + * @param $subject + * @return string + */ + protected function getToEmailAddress($subject): string + { + return $subject->getMessage()->getTo()[0]->getEmail() ?? ''; + } } diff --git a/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php b/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php new file mode 100644 index 0000000..952ef99 --- /dev/null +++ b/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php @@ -0,0 +1,92 @@ +scopeConfig->isSetFlag('system/smtp/disable', ScopeInterface::SCOPE_STORE) || + !$this->scopeConfig->isSetFlag(Emailcatcher::CONFIG_PATH_EMAIL_CATCHER_ENABLED, ScopeInterface::SCOPE_STORE) || + !$this->scopeConfig->isSetFlag(Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ADMIN_ALLOWED_ENABLED, ScopeInterface::SCOPE_STORE) + ) { + $proceed(); + return; + } + + $emailAddresses = []; + foreach ($subject->getMessage()->getTo() as $to) { + $emailAddresses[] = $to->getEmail(); + } + foreach ($subject->getMessage()->getCc() as $cc) { + $emailAddresses[] = $cc->getEmail(); + } + foreach ($subject->getMessage()->getBcc() as $bcc) { + $emailAddresses[] = $bcc->getEmail(); + } + if ($this->containsOnlyAdminUsers($emailAddresses)) { + $proceed(); + } + } + + /** + * Addresses contains a known admin user email address + * + * @param $emailAddresses + * @return bool + */ + public function containsOnlyAdminUsers($emailAddresses): bool + { + $nonAdminEmailAddresses = array_diff($emailAddresses, $this->getAdminAndCustomAllowedEmails()); + + return count($nonAdminEmailAddresses) === 0; + } + + /** + * @return array + */ + protected function getAdminAndCustomAllowedEmails(): array + { + return array_merge($this->getAdminEmails(), $this->getCustomAllowedEmails()); + } + + /** + * @return array + */ + protected function getAdminEmails(): array + { + $collection = $this->userCollectionFactory->create(); + + return $collection->addFieldToSelect('email')->getColumnValues('email'); + } + + protected function getCustomAllowedEmails() + { + return $this->scopeConfig->getValue( + Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ALLOWED_ADDRESSES, + ScopeInterface::SCOPE_STORE); + } + +} \ No newline at end of file diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index f9a7301..6d5de28 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -40,6 +40,36 @@ + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + 1 + + + + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + 1 + + + diff --git a/etc/config.xml b/etc/config.xml index 64f7a4c..3b3703f 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -10,6 +10,13 @@ 0 + + 0 + + + + 0 + \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml index 155585a..22cc083 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -2,10 +2,11 @@ - + - + + From b431bbfaf8a97cd98f23b59b29057132b4b020d5 Mon Sep 17 00:00:00 2001 From: martijn van haagen Date: Thu, 31 Oct 2024 16:23:34 +0100 Subject: [PATCH 2/3] [FEATURE][SPC-4644] Merged smtp admin allow email in emailcatcher + added email blacklist --- Model/Config/Source/EmailTemplates.php | 19 +----- Model/Emailcatcher.php | 59 +++++++++++++++++-- .../Mail/Template/TransportBuilder.php | 2 +- .../Framework/Mail/TransportInterface.php | 17 +++--- .../Mail/TransportInterfacePlugin.php | 50 ++++++++-------- .../Listing/Column/EmailcatcherActions.php | 8 +-- etc/di.xml | 2 +- 7 files changed, 94 insertions(+), 63 deletions(-) diff --git a/Model/Config/Source/EmailTemplates.php b/Model/Config/Source/EmailTemplates.php index 814e0a1..0abe948 100755 --- a/Model/Config/Source/EmailTemplates.php +++ b/Model/Config/Source/EmailTemplates.php @@ -13,16 +13,6 @@ class EmailTemplates implements ArrayInterface { - /** - * @var Config - */ - protected $templateConfig; - - /** - * @var CollectionFactory - */ - protected $emailTemplateCollectionFactory; - /** * EmailTemplates constructor. * @@ -30,12 +20,9 @@ class EmailTemplates implements ArrayInterface * @param CollectionFactory $emailTemplateCollectionFactory */ public function __construct( - Config $templateConfig, - CollectionFactory $emailTemplateCollectionFactory - ) { - $this->templateConfig = $templateConfig; - $this->emailTemplateCollectionFactory = $emailTemplateCollectionFactory; - } + protected Config $templateConfig, + protected CollectionFactory $emailTemplateCollectionFactory + ) {} /** * To option array diff --git a/Model/Emailcatcher.php b/Model/Emailcatcher.php index 08ad45a..1615806 100644 --- a/Model/Emailcatcher.php +++ b/Model/Emailcatcher.php @@ -21,7 +21,7 @@ class Emailcatcher extends AbstractModel const CONFIG_PATH_EMAIL_CATCHER_ENABLED = 'emailcatcher/general/enabled'; const CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ADMIN_ALLOWED_ENABLED = 'emailcatcher/development/enabled'; const CONFIG_PATH_WHITELIST_APPLY_WHITELIST = 'emailcatcher/whitelist/apply_whitelist'; - const CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST = 'emailcatcher/blacklist/apply_whitelist'; + const CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST = 'emailcatcher/blacklist/apply_blacklist'; const CONFIG_PATH_TEMPLATE_WHITELIST = 'emailcatcher/whitelist/email_templates'; const CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ALLOWED_ADDRESSES = 'emailcatcher/development/allow_email_addresses'; @@ -131,18 +131,69 @@ public function imapUtf8($string) return (function_exists('imap_utf8') && is_string($string)) ? imap_utf8($string) : $string; } + /** + * @return bool + */ public function blackListEnabled(): bool { - return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST, ScopeInterface::SCOPE_STORE); + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_BLACKLIST_APPLY_BlACKLIST, + ScopeInterface::SCOPE_STORE + ); } + /** + * @return bool + */ public function whitelistEnabled(): bool { - return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_WHITELIST_APPLY_WHITELIST, ScopeInterface::SCOPE_STORE); + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_WHITELIST_APPLY_WHITELIST, + ScopeInterface::SCOPE_STORE + ); } + /** + * @return mixed + */ + public function getWhitelistedTemplates(): mixed + { + return $this->scopeConfig->getValue( + Emailcatcher::CONFIG_PATH_TEMPLATE_WHITELIST, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * @return bool + */ public function emailCatcherEnabled(): bool { - return $this->scopeConfig->isSetFlag(self::CONFIG_PATH_EMAIL_CATCHER_ENABLED, ScopeInterface::SCOPE_STORE); + return $this->scopeConfig->isSetFlag( + self::CONFIG_PATH_EMAIL_CATCHER_ENABLED, + ScopeInterface::SCOPE_STORE + ); } + + /** + * @return bool + */ + public function developmentAdminAllowedEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ADMIN_ALLOWED_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * @return array|mixed + */ + public function getDevelopmentAdminAllowedEmailAddresses(): mixed + { + return $this->scopeConfig->getValue( + Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ALLOWED_ADDRESSES, + ScopeInterface::SCOPE_STORE) ?: []; + } + } diff --git a/Plugin/Magento/Framework/Mail/Template/TransportBuilder.php b/Plugin/Magento/Framework/Mail/Template/TransportBuilder.php index 7e2ae14..97cec1b 100644 --- a/Plugin/Magento/Framework/Mail/Template/TransportBuilder.php +++ b/Plugin/Magento/Framework/Mail/Template/TransportBuilder.php @@ -15,7 +15,7 @@ class TransportBuilder * @param CurrentTemplate $currentTemplate */ public function __construct( - private CurrentTemplate $currentTemplate + protected CurrentTemplate $currentTemplate ) { } diff --git a/Plugin/Magento/Framework/Mail/TransportInterface.php b/Plugin/Magento/Framework/Mail/TransportInterface.php index 08b2e03..3060cde 100644 --- a/Plugin/Magento/Framework/Mail/TransportInterface.php +++ b/Plugin/Magento/Framework/Mail/TransportInterface.php @@ -22,10 +22,10 @@ class TransportInterface * @param Emailcatcher $emailcatcher */ public function __construct( - private ScopeConfigInterface $scopeConfig, - private EmailcatcherFactory $emailCatcherFactory, - private CurrentTemplate $currentTemplate, - private Emailcatcher $emailcatcher + protected ScopeConfigInterface $scopeConfig, + protected EmailcatcherFactory $emailCatcherFactory, + protected CurrentTemplate $currentTemplate, + protected Emailcatcher $emailcatcher ) {} /** @@ -45,7 +45,7 @@ public function aroundSendMessage( } if ($this->emailcatcher->blackListEnabled() && in_array($this->getToEmailAddress($subject), $this->getBlacklistEmailAddresses())) { - $subject->getMessage()->setSubject('Prevent Being Sent'); + $subject->getMessage()->setSubject('Prevent Being Sent - Blacklisted Email Address'); $this->saveMessage($subject); return; @@ -80,10 +80,7 @@ private function saveMessage($subject): void */ private function getTemplateWhitelist(): array { - $templates = $this->scopeConfig->getValue( - Emailcatcher::CONFIG_PATH_TEMPLATE_WHITELIST, - ScopeInterface::SCOPE_STORE - ); + $templates = $this->emailcatcher->getWhitelistedTemplates(); return $templates ? explode(',', $templates) : []; } @@ -93,7 +90,7 @@ private function getTemplateWhitelist(): array */ protected function getBlacklistEmailAddresses() : array { - return $this->scopeConfig->getValue('prevent_sending_email/blacklist/block_email_addresses'); + return explode(',', $this->scopeConfig->getValue('emailcatcher/blacklist/block_email_addresses')); } /** diff --git a/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php b/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php index 952ef99..f9706d5 100644 --- a/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php +++ b/Plugin/Magento/Framework/Mail/TransportInterfacePlugin.php @@ -12,10 +12,12 @@ class TransportInterfacePlugin /** * @param ScopeConfigInterface $scopeConfig * @param UserCollectionFactory $userCollectionFactory + * @param Emailcatcher $emailcatcher */ public function __construct( - private ScopeConfigInterface $scopeConfig, - private UserCollectionFactory $userCollectionFactory + protected ScopeConfigInterface $scopeConfig, + protected UserCollectionFactory $userCollectionFactory, + protected Emailcatcher $emailcatcher ) {} /** @@ -28,32 +30,29 @@ public function aroundSendMessage( \Closure $proceed ): void { - if (!$this->scopeConfig->isSetFlag('system/smtp/disable', ScopeInterface::SCOPE_STORE) || - !$this->scopeConfig->isSetFlag(Emailcatcher::CONFIG_PATH_EMAIL_CATCHER_ENABLED, ScopeInterface::SCOPE_STORE) || - !$this->scopeConfig->isSetFlag(Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ADMIN_ALLOWED_ENABLED, ScopeInterface::SCOPE_STORE) - ) { + if (!$this->scopeConfig->isSetFlag('system/smtp/disable', ScopeInterface::SCOPE_STORE)) { $proceed(); return; } - $emailAddresses = []; - foreach ($subject->getMessage()->getTo() as $to) { - $emailAddresses[] = $to->getEmail(); - } - foreach ($subject->getMessage()->getCc() as $cc) { - $emailAddresses[] = $cc->getEmail(); - } - foreach ($subject->getMessage()->getBcc() as $bcc) { - $emailAddresses[] = $bcc->getEmail(); - } - if ($this->containsOnlyAdminUsers($emailAddresses)) { - $proceed(); + if ($this->emailcatcher->developmentAdminAllowedEnabled()) { + $emailAddresses = []; + foreach ($subject->getMessage()->getTo() as $to) { + $emailAddresses[] = $to->getEmail(); + } + foreach ($subject->getMessage()->getCc() as $cc) { + $emailAddresses[] = $cc->getEmail(); + } + foreach ($subject->getMessage()->getBcc() as $bcc) { + $emailAddresses[] = $bcc->getEmail(); + } + if ($this->containsOnlyAdminUsers($emailAddresses)) { + $proceed(); + } } } /** - * Addresses contains a known admin user email address - * * @param $emailAddresses * @return bool */ @@ -82,11 +81,14 @@ protected function getAdminEmails(): array return $collection->addFieldToSelect('email')->getColumnValues('email'); } - protected function getCustomAllowedEmails() + /** + * @return array + */ + protected function getCustomAllowedEmails(): array { - return $this->scopeConfig->getValue( - Emailcatcher::CONFIG_PATH_DEVELOPMENT_EMAIL_CATCHER_ALLOWED_ADDRESSES, - ScopeInterface::SCOPE_STORE); + $customEmails = $this->emailcatcher->getDevelopmentAdminAllowedEmailAddresses(); + + return !is_array($customEmails) ? explode(',', $customEmails) : $customEmails; } } \ No newline at end of file diff --git a/Ui/Component/Listing/Column/EmailcatcherActions.php b/Ui/Component/Listing/Column/EmailcatcherActions.php index 49ba55e..ac9d676 100644 --- a/Ui/Component/Listing/Column/EmailcatcherActions.php +++ b/Ui/Component/Listing/Column/EmailcatcherActions.php @@ -19,11 +19,6 @@ class EmailcatcherActions extends \Magento\Ui\Component\Listing\Columns\Column const URL_PATH_SEND = 'experius_emailcatcher/emailcatcher/send'; const URL_PATH_FORWARD = 'experius_emailcatcher/emailcatcher/forward'; - /** - * @var UrlInterface - */ - protected $urlBuilder; - /** * EmailcatcherActions constructor. * @@ -36,12 +31,11 @@ class EmailcatcherActions extends \Magento\Ui\Component\Listing\Columns\Column public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, - UrlInterface $urlBuilder, + protected UrlInterface $urlBuilder, array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); - $this->urlBuilder = $urlBuilder; } /** diff --git a/etc/di.xml b/etc/di.xml index 22cc083..746e5a8 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -6,7 +6,7 @@ - + From 1c0373dbd3c269990e8254a94bc5930507deff2f Mon Sep 17 00:00:00 2001 From: martijn van haagen Date: Fri, 8 Nov 2024 14:03:46 +0000 Subject: [PATCH 3/3] [FEATURE][SPC-4644] Extended readme --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 257a293..76cc900 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ - Resend a caught email - Cleanup emails older than 30 days (cron or manual) - Send emails based on whitelisted email templates + - Block emails based on blacklisted email list + - Enable admin user to receive email while magento email communication is disabled + ## Versions @@ -56,5 +59,13 @@ Utilise whitelist functionality - Stores > Settings > Configuration > Advanced > Email Catcher > Whitelist > Apply whitelist (emailcatcher/whitelist/apply_whitelist) - Stores > Settings > Configuration > Advanced > Email Catcher > Whitelist > Whitelisted templates (emailcatcher/whitelist/email_templates) +Utilise blacklist functionality +- Stores > Settings > Configuration > Advanced > Email Catcher > Blacklist > Apply blacklist (emailcatcher/blacklist/apply_blacklist) +- Stores > Settings > Configuration > Advanced > Email Catcher > Blacklist > Blacklisted templates (emailcatcher/blacklist/block_email_addresses) + +Utilise enable admin user receive email +- Stores > Settings > Configuration > Advanced > Email Catcher > General > Enable Admin User Receive Email (emailcatcher/development/enabled) +- Stores > Settings > Configuration > Advanced > Email Catcher > General > Admin User Email (emailcatcher/development/allow_email_addresses) + Admin grid - System > Tools > Email Catcher