From 3001bc3479fdae50813d968ba3e6fce68bcb1ac6 Mon Sep 17 00:00:00 2001
From: Michael Lee <lee.5151@osu.edu>
Date: Fri, 15 Oct 2021 15:23:27 -0400
Subject: [PATCH] add queue_mail module

---
 composer.json                                 |   1 +
 composer.lock                                 |  61 +++-
 vendor/composer/autoload_classmap.php         |   2 -
 vendor/composer/autoload_static.php           |   2 -
 vendor/composer/installed.json                |  62 ++++
 vendor/composer/installed.php                 |  13 +-
 web/modules/queue_mail/LICENSE.txt            | 339 ++++++++++++++++++
 web/modules/queue_mail/README.md              |  54 +++
 web/modules/queue_mail/composer.json          |  25 ++
 .../config/install/queue_mail.settings.yml    |   5 +
 .../config/schema/queue_mail.schema.yml       |  19 +
 .../queue_mail_language.info.yml              |  12 +
 .../queue_mail_language.module                |  15 +
 .../queue_mail_language.services.yml          |   6 +
 .../LanguageAwareSendMailQueueWorker.php      | 107 ++++++
 .../src/QueueMailLanguageNegotiator.php       |  48 +++
 .../QueueMailLanguageFunctionalTest.php       |  30 ++
 web/modules/queue_mail/queue_mail.api.php     |  69 ++++
 web/modules/queue_mail/queue_mail.info.yml    |  11 +
 web/modules/queue_mail/queue_mail.install     |  86 +++++
 .../queue_mail/queue_mail.links.menu.yml      |   5 +
 web/modules/queue_mail/queue_mail.module      |  66 ++++
 web/modules/queue_mail/queue_mail.routing.yml |   7 +
 .../src/Form/QueueMailSettingsForm.php        | 197 ++++++++++
 .../QueueWorker/SendMailQueueWorker.php       | 296 +++++++++++++++
 .../queue_mail_test/queue_mail_test.info.yml  |  14 +
 .../queue_mail_test/queue_mail_test.module    |  34 ++
 .../Mail/QueueMailTestMailCollector.php       |  41 +++
 .../Functional/QueueMailConfigurationTest.php | 110 ++++++
 .../Functional/QueueMailFunctionalTest.php    | 251 +++++++++++++
 30 files changed, 1981 insertions(+), 7 deletions(-)
 create mode 100644 web/modules/queue_mail/LICENSE.txt
 create mode 100644 web/modules/queue_mail/README.md
 create mode 100644 web/modules/queue_mail/composer.json
 create mode 100644 web/modules/queue_mail/config/install/queue_mail.settings.yml
 create mode 100644 web/modules/queue_mail/config/schema/queue_mail.schema.yml
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.info.yml
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.module
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.services.yml
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/src/Plugin/QueueWorker/LanguageAwareSendMailQueueWorker.php
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/src/QueueMailLanguageNegotiator.php
 create mode 100644 web/modules/queue_mail/modules/queue_mail_language/tests/src/Functional/QueueMailLanguageFunctionalTest.php
 create mode 100644 web/modules/queue_mail/queue_mail.api.php
 create mode 100644 web/modules/queue_mail/queue_mail.info.yml
 create mode 100644 web/modules/queue_mail/queue_mail.install
 create mode 100644 web/modules/queue_mail/queue_mail.links.menu.yml
 create mode 100644 web/modules/queue_mail/queue_mail.module
 create mode 100644 web/modules/queue_mail/queue_mail.routing.yml
 create mode 100644 web/modules/queue_mail/src/Form/QueueMailSettingsForm.php
 create mode 100644 web/modules/queue_mail/src/Plugin/QueueWorker/SendMailQueueWorker.php
 create mode 100644 web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.info.yml
 create mode 100644 web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.module
 create mode 100644 web/modules/queue_mail/tests/modules/queue_mail_test/src/Plugin/Mail/QueueMailTestMailCollector.php
 create mode 100644 web/modules/queue_mail/tests/src/Functional/QueueMailConfigurationTest.php
 create mode 100644 web/modules/queue_mail/tests/src/Functional/QueueMailFunctionalTest.php

diff --git a/composer.json b/composer.json
index e411af3744..f7dd56b9cf 100644
--- a/composer.json
+++ b/composer.json
@@ -144,6 +144,7 @@
         "drupal/pantheon_advanced_page_cache": "1.2",
         "drupal/paragraphs": "1.12",
         "drupal/pathauto": "1.8",
+        "drupal/queue_mail": "^1.4",
         "drupal/rebuild_cache_access": "1.7",
         "drupal/recaptcha": "3.0",
         "drupal/recaptcha_v3": "^1.4",
diff --git a/composer.lock b/composer.lock
index 956384227e..cdcf77dde7 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": "8ac8778d5044260fcd494ebe07b01805",
+    "content-hash": "39b546aa80d3dd2636b7dd5693e7c94e",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -6176,6 +6176,65 @@
                 "documentation": "https://www.drupal.org/docs/8/modules/pathauto"
             }
         },
+        {
+            "name": "drupal/queue_mail",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://git.drupalcode.org/project/queue_mail.git",
+                "reference": "8.x-1.4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://ftp.drupal.org/files/projects/queue_mail-8.x-1.4.zip",
+                "reference": "8.x-1.4",
+                "shasum": "ccc3603d084017ec4d3aa67fa60da171ef8d63b8"
+            },
+            "require": {
+                "drupal/core": "^8.8.0 || ^9.0"
+            },
+            "type": "drupal-module",
+            "extra": {
+                "drupal": {
+                    "version": "8.x-1.4",
+                    "datestamp": "1632316348",
+                    "security-coverage": {
+                        "status": "covered",
+                        "message": "Covered by Drupal's security advisory policy"
+                    }
+                }
+            },
+            "notification-url": "https://packages.drupal.org/8/downloads",
+            "license": [
+                "GPL-2.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Evgenii Nikitin (sinn)",
+                    "homepage": "https://www.drupal.org/u/sinn",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Steven Jones",
+                    "homepage": "https://www.drupal.org/user/99644",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "sinn",
+                    "homepage": "https://www.drupal.org/user/682600"
+                },
+                {
+                    "name": "wafaa",
+                    "homepage": "https://www.drupal.org/user/50133"
+                }
+            ],
+            "description": "Queues all mail sent by your Drupal site so that it is sent via cron using the Drupal Queue API.",
+            "homepage": "http://drupal.org/project/queue_mail",
+            "support": {
+                "source": "https://git.drupalcode.org/project/queue_mail",
+                "issues": "https://www.drupal.org/project/issues/queue_mail"
+            }
+        },
         {
             "name": "drupal/rebuild_cache_access",
             "version": "1.7.0",
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 70c4702c7d..4bfc4c4a72 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -2258,8 +2258,6 @@
     'Drupal\\Core\\Language\\LanguageManager' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
     'Drupal\\Core\\Language\\LanguageManagerInterface' => $baseDir . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
     'Drupal\\Core\\Layout\\Annotation\\Layout' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-    'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-    'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => $baseDir . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
     'Drupal\\Core\\Layout\\LayoutDefault' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
     'Drupal\\Core\\Layout\\LayoutDefinition' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
     'Drupal\\Core\\Layout\\LayoutInterface' => $baseDir . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 7859093792..812f71f45d 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -3049,8 +3049,6 @@ class ComposerStaticInit5c689ffcd54b9e495ed983fdce09b530
         'Drupal\\Core\\Language\\LanguageManager' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManager.php',
         'Drupal\\Core\\Language\\LanguageManagerInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Language/LanguageManagerInterface.php',
         'Drupal\\Core\\Layout\\Annotation\\Layout' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Annotation/Layout.php',
-        'Drupal\\Core\\Layout\\Icon\\IconBuilderInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php',
-        'Drupal\\Core\\Layout\\Icon\\SvgIconBuilder' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php',
         'Drupal\\Core\\Layout\\LayoutDefault' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefault.php',
         'Drupal\\Core\\Layout\\LayoutDefinition' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutDefinition.php',
         'Drupal\\Core\\Layout\\LayoutInterface' => __DIR__ . '/../..' . '/web/core/lib/Drupal/Core/Layout/LayoutInterface.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 46e37f51f9..deb1af93f8 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -6413,6 +6413,68 @@
             },
             "install-path": "../../web/modules/pathauto"
         },
+        {
+            "name": "drupal/queue_mail",
+            "version": "1.4.0",
+            "version_normalized": "1.4.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://git.drupalcode.org/project/queue_mail.git",
+                "reference": "8.x-1.4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://ftp.drupal.org/files/projects/queue_mail-8.x-1.4.zip",
+                "reference": "8.x-1.4",
+                "shasum": "ccc3603d084017ec4d3aa67fa60da171ef8d63b8"
+            },
+            "require": {
+                "drupal/core": "^8.8.0 || ^9.0"
+            },
+            "type": "drupal-module",
+            "extra": {
+                "drupal": {
+                    "version": "8.x-1.4",
+                    "datestamp": "1632316348",
+                    "security-coverage": {
+                        "status": "covered",
+                        "message": "Covered by Drupal's security advisory policy"
+                    }
+                }
+            },
+            "installation-source": "dist",
+            "notification-url": "https://packages.drupal.org/8/downloads",
+            "license": [
+                "GPL-2.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Evgenii Nikitin (sinn)",
+                    "homepage": "https://www.drupal.org/u/sinn",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "Steven Jones",
+                    "homepage": "https://www.drupal.org/user/99644",
+                    "role": "Maintainer"
+                },
+                {
+                    "name": "sinn",
+                    "homepage": "https://www.drupal.org/user/682600"
+                },
+                {
+                    "name": "wafaa",
+                    "homepage": "https://www.drupal.org/user/50133"
+                }
+            ],
+            "description": "Queues all mail sent by your Drupal site so that it is sent via cron using the Drupal Queue API.",
+            "homepage": "http://drupal.org/project/queue_mail",
+            "support": {
+                "source": "https://git.drupalcode.org/project/queue_mail",
+                "issues": "https://www.drupal.org/project/issues/queue_mail"
+            },
+            "install-path": "../../web/modules/queue_mail"
+        },
         {
             "name": "drupal/rebuild_cache_access",
             "version": "1.7.0",
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 1aecc9a51a..9677e42e69 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => '12c2eafc0e95f979c96849005ecf8e09d6dcb2ae',
+        'reference' => 'd2ba02375d2963b4d365129930f11ab04573ee3e',
         'name' => 'osu-asc-webservices/d8-upstream',
         'dev' => true,
     ),
@@ -1414,6 +1414,15 @@
             'reference' => '8.x-1.8',
             'dev_requirement' => false,
         ),
+        'drupal/queue_mail' => array(
+            'pretty_version' => '1.4.0',
+            'version' => '1.4.0.0',
+            'type' => 'drupal-module',
+            'install_path' => __DIR__ . '/../../web/modules/queue_mail',
+            'aliases' => array(),
+            'reference' => '8.x-1.4',
+            'dev_requirement' => false,
+        ),
         'drupal/quickedit' => array(
             'dev_requirement' => false,
             'replaced' => array(
@@ -2086,7 +2095,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => '12c2eafc0e95f979c96849005ecf8e09d6dcb2ae',
+            'reference' => 'd2ba02375d2963b4d365129930f11ab04573ee3e',
             'dev_requirement' => false,
         ),
         'pantheon-systems/quicksilver-pushback' => array(
diff --git a/web/modules/queue_mail/LICENSE.txt b/web/modules/queue_mail/LICENSE.txt
new file mode 100644
index 0000000000..d159169d10
--- /dev/null
+++ b/web/modules/queue_mail/LICENSE.txt
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/web/modules/queue_mail/README.md b/web/modules/queue_mail/README.md
new file mode 100644
index 0000000000..28de7596c5
--- /dev/null
+++ b/web/modules/queue_mail/README.md
@@ -0,0 +1,54 @@
+Queue mail
+==========
+
+Enable queue for mails
+----------------------
+Queue mail module does not put your mails to queue after enabling. 
+By default this feature is disabled.
+
+You should go to the settings page /admin/config/system/queue_mail 
+and add mail IDs to the "Mail IDs to queue" field.
+
+Use "*" to enable queue for sending all emails.
+
+When Queue mail processes mails it marks all mails as queued or not.
+So you can check status of mail in your code:
+
+```
+$message = \Drupal::service('plugin.manager.mail')->mail();
+if ($message['queued']] {
+  // Message has been added to the queue.
+}
+else {
+  // Message has not been added to the queue. 
+}
+``` 
+
+Language
+--------
+If you need full language support in mail formatting please enable 
+Queue Mail Language (queue_mail_language) module. It will use language
+of mail instead of default system language in mail formatting.
+
+Drush
+-----
+Drush has his own command to process specific queue: 
+
+`drush queue-run QUEUE_NAME`
+
+So you can use command to run queue_mail worker:
+
+`drush queue-run queue_mail --time-limit=15` 
+
+__Note__: you have to use time-limit option with "drush queue-run queue_mail"
+because "Queue processing time" setting doesn't work in this case. If system
+can't send mails it adds them back to queue. Without time-limit option this 
+process won't be finished.
+
+API
+---
+Queue mail implements hook_queue_mail_send_alter(). This hook is very similar
+hook_mail_alter() and allows change mail message right before sending.
+
+See queue_mail.api.php for more information.
+
diff --git a/web/modules/queue_mail/composer.json b/web/modules/queue_mail/composer.json
new file mode 100644
index 0000000000..e4e8b980b0
--- /dev/null
+++ b/web/modules/queue_mail/composer.json
@@ -0,0 +1,25 @@
+{
+    "name": "drupal/queue_mail",
+    "description": "Queues all mail sent by your Drupal site so that it is sent via cron using the Drupal Queue API.",
+    "type": "drupal-module",
+    "homepage": "http://drupal.org/project/queue_mail",
+    "authors": [
+        {
+            "name": "Evgenii Nikitin (sinn)",
+            "homepage": "https://www.drupal.org/u/sinn",
+            "role": "Maintainer"
+        },
+        {
+            "name": "Steven Jones",
+            "homepage": "https://www.drupal.org/user/99644",
+            "role": "Maintainer"
+        }
+    ],
+    "support": {
+        "issues": "https://www.drupal.org/project/issues/queue_mail",
+        "source": "https://git.drupalcode.org/project/queue_mail"
+    },
+    "require": {
+        "drupal/core": "^8.8.0 || ^9.0"
+    }
+}
diff --git a/web/modules/queue_mail/config/install/queue_mail.settings.yml b/web/modules/queue_mail/config/install/queue_mail.settings.yml
new file mode 100644
index 0000000000..bffcfd1960
--- /dev/null
+++ b/web/modules/queue_mail/config/install/queue_mail.settings.yml
@@ -0,0 +1,5 @@
+queue_mail_keys: ''
+queue_mail_queue_time: 15
+queue_mail_queue_wait_time: 0
+threshold: 50
+requeue_interval: 10800
diff --git a/web/modules/queue_mail/config/schema/queue_mail.schema.yml b/web/modules/queue_mail/config/schema/queue_mail.schema.yml
new file mode 100644
index 0000000000..39f06de127
--- /dev/null
+++ b/web/modules/queue_mail/config/schema/queue_mail.schema.yml
@@ -0,0 +1,19 @@
+queue_mail.settings:
+  type: config_object
+  label: 'Queue mail settings'
+  mapping:
+    queue_mail_keys:
+      type: string
+      label: 'Mail IDs to queue'
+    queue_mail_queue_time:
+      type: integer
+      label: 'Queue processing time (max)'
+    queue_mail_queue_wait_time:
+      type: integer
+      label: 'Wait time per item'
+    threshold:
+      type: integer
+      label: 'Queue retry threshold'
+    requeue_interval:
+      type: integer
+      label: 'Requeue interval'
diff --git a/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.info.yml b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.info.yml
new file mode 100644
index 0000000000..610390bbf7
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.info.yml
@@ -0,0 +1,12 @@
+name: Queue Mail Language
+type: module
+core_version_requirement: ^8 || ^9
+description: Adds language support for queued mails.
+package: Mail
+dependencies:
+  - drupal:language
+
+# Information added by Drupal.org packaging script on 2021-09-22
+version: '8.x-1.4'
+project: 'queue_mail'
+datestamp: 1632316351
diff --git a/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.module b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.module
new file mode 100644
index 0000000000..58058499d4
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * The Queue Mail Language module.
+ */
+
+use Drupal\queue_mail_language\Plugin\QueueWorker\LanguageAwareSendMailQueueWorker;
+
+/**
+ * Implements hook_queue_info_alter().
+ */
+function queue_mail_language_queue_info_alter(&$queues) {
+  $queues['queue_mail']['class'] = LanguageAwareSendMailQueueWorker::class;
+}
diff --git a/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.services.yml b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.services.yml
new file mode 100644
index 0000000000..de1fc2867a
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/queue_mail_language.services.yml
@@ -0,0 +1,6 @@
+services:
+  queue_mail.language_negotiator:
+    class: Drupal\queue_mail_language\QueueMailLanguageNegotiator
+    arguments: ['@language_manager', '@plugin.manager.language_negotiation_method', '@config.factory', '@settings', '@request_stack']
+    calls:
+      - [initLanguageManager]
diff --git a/web/modules/queue_mail/modules/queue_mail_language/src/Plugin/QueueWorker/LanguageAwareSendMailQueueWorker.php b/web/modules/queue_mail/modules/queue_mail_language/src/Plugin/QueueWorker/LanguageAwareSendMailQueueWorker.php
new file mode 100644
index 0000000000..d71622c1ec
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/src/Plugin/QueueWorker/LanguageAwareSendMailQueueWorker.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\queue_mail_language\Plugin\QueueWorker;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Mail\MailManagerInterface;
+use Drupal\Core\Theme\ThemeInitializationInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\language\ConfigurableLanguageManagerInterface;
+use Drupal\queue_mail\Plugin\QueueWorker\SendMailQueueWorker;
+use Drupal\queue_mail_language\QueueMailLanguageNegotiator;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Sends emails from queue with language support.
+ */
+class LanguageAwareSendMailQueueWorker extends SendMailQueueWorker {
+
+  /**
+   * The configurable language manager.
+   *
+   * @var \Drupal\language\ConfigurableLanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The queue mail language negotiator.
+   *
+   * @var \Drupal\queue_mail_language\QueueMailLanguageNegotiator
+   */
+  protected $queueMailLanguageNegotiator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('theme.manager'),
+      $container->get('theme.initialization'),
+      $container->get('plugin.manager.mail'),
+      $container->get('logger.factory'),
+      $container->get('config.factory'),
+      $container->get('queue'),
+      $container->get('module_handler'),
+      $container->get('language_manager'),
+      $container->get('queue_mail.language_negotiator')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(
+    ThemeManagerInterface $theme_manager,
+    ThemeInitializationInterface $theme_init,
+    MailManagerInterface $mail_manager,
+    LoggerChannelFactoryInterface $logger_factory,
+    ConfigFactoryInterface $config_factory,
+    ContainerAwareInterface $queue_factory,
+    ModuleHandlerInterface $module_handler,
+    ConfigurableLanguageManagerInterface $language_manager,
+    QueueMailLanguageNegotiator $queue_mail_language_negotiator
+  ) {
+    parent::__construct($theme_manager, $theme_init, $mail_manager, $logger_factory, $config_factory, $queue_factory, $module_handler);
+    $this->languageManager = $language_manager;
+    $this->queueMailLanguageNegotiator = $queue_mail_language_negotiator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setMailLanguage(array $message) {
+    $default_langcode = $this->languageManager->getDefaultLanguage()->getId();
+    if ($message['langcode'] !== $default_langcode) {
+      $this->setNegotiatorLanguage($message['langcode']);
+    }
+    return $default_langcode;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setActiveLanguage(array $message, $langcode) {
+    if ($message['langcode'] !== $langcode) {
+      $this->setNegotiatorLanguage($langcode);
+    }
+  }
+
+  /**
+   * Sets the queue mail negotiator language.
+   *
+   * @param string $langcode
+   *   The new language code.
+   */
+  protected function setNegotiatorLanguage($langcode) {
+    if ($this->languageManager->getNegotiator() !== $this->queueMailLanguageNegotiator) {
+      $this->languageManager->setNegotiator($this->queueMailLanguageNegotiator);
+    }
+    $this->queueMailLanguageNegotiator->setLanguageCode($langcode);
+    // Needed to re-run language negotiation.
+    $this->languageManager->reset();
+  }
+
+}
diff --git a/web/modules/queue_mail/modules/queue_mail_language/src/QueueMailLanguageNegotiator.php b/web/modules/queue_mail/modules/queue_mail_language/src/QueueMailLanguageNegotiator.php
new file mode 100644
index 0000000000..babcc0f4d1
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/src/QueueMailLanguageNegotiator.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\queue_mail_language;
+
+use Drupal\language\LanguageNegotiator;
+
+/**
+ * Class responsible for performing language negotiation.
+ */
+class QueueMailLanguageNegotiator extends LanguageNegotiator {
+
+  /**
+   * Language code.
+   *
+   * @var string
+   */
+  public $languageCode = NULL;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initializeType($type) {
+    $language = NULL;
+    $method_id = static::METHOD_ID;
+    $availableLanguages = $this->languageManager->getLanguages();
+
+    if ($this->languageCode && isset($availableLanguages[$this->languageCode])) {
+      $language = $availableLanguages[$this->languageCode];
+    }
+    else {
+      // If no other language was found use the default one.
+      $language = $this->languageManager->getDefaultLanguage();
+    }
+
+    return [$method_id => $language];
+  }
+
+  /**
+   * Sets language code.
+   *
+   * @param string $langcode
+   *   Language code.
+   */
+  public function setLanguageCode($langcode) {
+    $this->languageCode = $langcode;
+  }
+
+}
diff --git a/web/modules/queue_mail/modules/queue_mail_language/tests/src/Functional/QueueMailLanguageFunctionalTest.php b/web/modules/queue_mail/modules/queue_mail_language/tests/src/Functional/QueueMailLanguageFunctionalTest.php
new file mode 100644
index 0000000000..03178f04e1
--- /dev/null
+++ b/web/modules/queue_mail/modules/queue_mail_language/tests/src/Functional/QueueMailLanguageFunctionalTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Tests\queue_mail_language\Functional;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\queue_mail\Functional\QueueMailFunctionalTest;
+
+/**
+ * Tests queue mail functionality with language support.
+ *
+ * @group queue_mail
+ */
+class QueueMailLanguageFunctionalTest extends QueueMailFunctionalTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['queue_mail_language', 'queue_mail_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp():void {
+    parent::setUp();
+
+    $this->langcode = 'it';
+    ConfigurableLanguage::createFromLangcode($this->langcode)->save();
+  }
+
+}
diff --git a/web/modules/queue_mail/queue_mail.api.php b/web/modules/queue_mail/queue_mail.api.php
new file mode 100644
index 0000000000..171f7bae57
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.api.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Documentation for Queue Mail API.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Alter a queued email when it is dequeued and about to be sent.
+ *
+ * This hook is largely equivalent to hook_mail_alter(), which will have already
+ * been called for this email, but as sending can be delayed for some time, if
+ * you need to do specific things just before the email is actually sent, you
+ * can use this hook.
+ *
+ * @param array $message
+ *   An array containing the message data. Keys in this array include:
+ *   - 'id':
+ *     The MailManagerInterface->mail() id of the message. Look at module source
+ *     code or MailManagerInterface->mail() for possible id values.
+ *   - 'to':
+ *     The address or addresses the message will be sent to. The
+ *     formatting of this string must comply with RFC 2822.
+ *   - 'from':
+ *     The address the message will be marked as being from, which is
+ *     either a custom address or the site-wide default email address.
+ *   - 'subject':
+ *     Subject of the email to be sent. This must not contain any newline
+ *     characters, or the email may not be sent properly.
+ *   - 'body':
+ *     An array of strings or objects that implement
+ *     \Drupal\Component\Render\MarkupInterface containing the message text. The
+ *     message body is created by concatenating the individual array strings
+ *     into a single text string using "\n\n" as a separator.
+ *   - 'headers':
+ *     Associative array containing mail headers, such as From, Sender,
+ *     MIME-Version, Content-Type, etc.
+ *   - 'params':
+ *     An array of optional parameters supplied by the caller of
+ *     MailManagerInterface->mail() that is used to build the message before
+ *     hook_mail_alter() is invoked.
+ *   - 'language':
+ *     The language object used to build the message before hook_mail_alter()
+ *     is invoked.
+ *   - 'send':
+ *     Set to FALSE to abort sending this email message.
+ *
+ * @see \Drupal\Core\Mail\MailManagerInterface::mail()
+ */
+function hook_queue_mail_send_alter(array &$message) {
+  if ($message['id'] == 'modulename_messagekey') {
+    if (!example_notifications_optin($message['to'], $message['id'])) {
+      // If the recipient has opted to not receive such messages, cancel
+      // sending.
+      $message['send'] = FALSE;
+      return;
+    }
+    $message['body'][] = "--\nMail sent out from " . \Drupal::config('system.site')->get('name');
+  }
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/web/modules/queue_mail/queue_mail.info.yml b/web/modules/queue_mail/queue_mail.info.yml
new file mode 100644
index 0000000000..a7c1cfba71
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.info.yml
@@ -0,0 +1,11 @@
+name: Queue Mail
+type: module
+core_version_requirement: ^8 || ^9
+description: Queues all mail sent by your Drupal site so that it is sent via cron using the Drupal Queue API. This is helpful for large traffic sites where sending a lot of emails per page request can slow things down considerably.
+package: Mail
+configure: queue_mail.admin_settings
+
+# Information added by Drupal.org packaging script on 2021-09-22
+version: '8.x-1.4'
+project: 'queue_mail'
+datestamp: 1632316351
diff --git a/web/modules/queue_mail/queue_mail.install b/web/modules/queue_mail/queue_mail.install
new file mode 100644
index 0000000000..66a7183758
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.install
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Queue Mail module.
+ */
+
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_install().
+ */
+function queue_mail_install() {
+  _queue_mail_get_queue()->createQueue();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function queue_mail_uninstall() {
+  _queue_mail_get_queue()->deleteQueue();
+}
+
+/**
+ * Implements hook_requirements().
+ */
+function queue_mail_requirements($phase) {
+  $requirements = [];
+
+  switch ($phase) {
+    case 'runtime':
+      $queue_length = _queue_mail_get_queue()->numberOfItems();
+      $requirements['queue_mail'] = [
+        'title' => t('Queue mail'),
+        'value' => \Drupal::translation()->formatPlural(
+          $queue_length,
+          '1 mail currently queued for sending.',
+          '@count mails currently queued for sending.'),
+        'description' => [
+          [
+            '#markup' => t('Sending of queued mails happens on cron.'),
+            '#suffix' => ' ',
+          ],
+          [
+            '#markup' => t('You can <a href=":cron">run cron manually</a>.',
+              [':cron' => Url::fromRoute('system.run_cron')->toString()]),
+            '#prefix' => '<br/>',
+          ],
+        ],
+        'severity' => $queue_length > 0 ? REQUIREMENT_INFO : REQUIREMENT_OK,
+      ];
+      break;
+  }
+
+  return $requirements;
+}
+
+/**
+ * Set configuration settings for a retry limit.
+ */
+function queue_mail_update_8001() {
+  \Drupal::configFactory()
+    ->getEditable('queue_mail.settings')
+    ->set('threshold', 50)
+    ->set('requeue_interval', 10800)
+    ->save();
+}
+
+/**
+ * Set configuration settings for a wait time per item.
+ */
+function queue_mail_update_8002() {
+  \Drupal::configFactory()
+    ->getEditable('queue_mail.settings')
+    ->set('queue_mail_queue_wait_time', 0)
+    ->save();
+}
+
+/**
+ * Enable "queue_mail_language" to preserve backwards-compatibility.
+ */
+function queue_mail_update_8003() {
+  /** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
+  $module_installer = \Drupal::service('module_installer');
+  $module_installer->install(['queue_mail_language']);
+}
diff --git a/web/modules/queue_mail/queue_mail.links.menu.yml b/web/modules/queue_mail/queue_mail.links.menu.yml
new file mode 100644
index 0000000000..8dc5eadee3
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.links.menu.yml
@@ -0,0 +1,5 @@
+queue_mail.admin_settings:
+  title: Queue Mail
+  route_name: queue_mail.admin_settings
+  parent: system.admin_config_system
+  description: 'Configure mails that should be queued for sending on cron instead of immediately.'
diff --git a/web/modules/queue_mail/queue_mail.module b/web/modules/queue_mail/queue_mail.module
new file mode 100644
index 0000000000..5647ea7a6b
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.module
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * The Queue Mail module.
+ */
+
+/**
+ * Get an instance of the mail queue.
+ */
+function _queue_mail_get_queue() {
+  return \Drupal::queue('queue_mail', TRUE);
+}
+
+/**
+ * Implements hook_mail_alter().
+ */
+function queue_mail_mail_alter(&$message) {
+  $message['queued'] = FALSE;
+
+  // Avoid queueing a message if it is not going to be sent, as it would be
+  // skipped by our queue worker anyway.
+  if (empty($message['send'])) {
+    return;
+  }
+
+  $config = \Drupal::config('queue_mail.settings');
+  $mail_keys = $config->get('queue_mail_keys');
+
+  if (\Drupal::service('path.matcher')->matchPath($message['id'], $mail_keys)) {
+    // Save theme that is used to format mail.
+    $message['theme'] = \Drupal::service('theme.manager')->getActiveTheme()->getName();
+    // Add message to the queue.
+    $id = _queue_mail_get_queue()->createItem($message);
+    // Was the message added to queue?
+    $message['queued'] = $id ? TRUE : FALSE;
+    // Prevent the message from being sent instantly.
+    $message['send'] = FALSE;
+  }
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function queue_mail_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'mail_alter') {
+    // Ensure that our hook_mail_alter implementation is always called last.
+    if (isset($implementations['queue_mail'])) {
+      $group = $implementations['queue_mail'];
+      unset($implementations['queue_mail']);
+      // Now add it back, which will ensure we are called last.
+      $implementations['queue_mail'] = $group;
+    }
+  }
+}
+
+/**
+ * Implements hook_queue_info_alter().
+ */
+function queue_mail_queue_info_alter(&$queues) {
+  $queue_time = \Drupal::config('queue_mail.settings')->get('queue_mail_queue_time');
+  if (!empty($queue_time)) {
+    $queues['queue_mail']['cron']['time'] = $queue_time;
+  }
+
+}
diff --git a/web/modules/queue_mail/queue_mail.routing.yml b/web/modules/queue_mail/queue_mail.routing.yml
new file mode 100644
index 0000000000..24096d8e06
--- /dev/null
+++ b/web/modules/queue_mail/queue_mail.routing.yml
@@ -0,0 +1,7 @@
+queue_mail.admin_settings:
+  path: '/admin/config/system/queue_mail'
+  defaults:
+    _form: '\Drupal\queue_mail\Form\QueueMailSettingsForm'
+    _title:  'Queue Mail'
+  requirements:
+    _permission: 'administer site configuration'
diff --git a/web/modules/queue_mail/src/Form/QueueMailSettingsForm.php b/web/modules/queue_mail/src/Form/QueueMailSettingsForm.php
new file mode 100644
index 0000000000..48df6dfb5e
--- /dev/null
+++ b/web/modules/queue_mail/src/Form/QueueMailSettingsForm.php
@@ -0,0 +1,197 @@
+<?php
+
+namespace Drupal\queue_mail\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Routing\RedirectDestinationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Configures module's settings.
+ */
+class QueueMailSettingsForm extends ConfigFormBase {
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The redirect destination.
+   *
+   * @var \Drupal\Core\Routing\RedirectDestinationInterface
+   */
+  protected $redirectDestination;
+
+  /**
+   * Constructs a new QueueMailSettingsForm.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   Module handler service.
+   * @param Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
+   *   The redirect destination.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, RedirectDestinationInterface $redirect_destination) {
+    parent::__construct($config_factory);
+
+    $this->moduleHandler = $module_handler;
+    $this->redirectDestination = $redirect_destination;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('module_handler'),
+      $container->get('redirect.destination')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'queue_mail_settings';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'queue_mail.settings',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->configFactory->get('queue_mail.settings');
+
+    $form['queue_status'] = [
+      '#type' => 'item',
+      '#title' => $this->t('Queue status'),
+      '#markup' => $this->formatPlural(_queue_mail_get_queue()->numberOfItems(), '1 mail currently queued for sending.', '@count mails currently queued for sending.'),
+      '#description' => $this->t('Sending of queued mails happens on cron. You can <a href="@cron_link">run cron manually</a>.',
+        [
+          '@cron_link' => Url::fromRoute('system.run_cron', [], ['query' => $this->redirectDestination->getAsArray()])->toString(),
+        ]),
+    ];
+
+    $form['queue_mail_keys'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('Mail IDs to queue'),
+      '#description' => $this->t('Enter each mail ID to queue on a separate line. Use <strong>*</strong> to do a wildcard match.') . '<br/>' . $this->t('Mail IDs are a combination of the first and second arguments sent to <em>drupal_mail</em> when a module sends an email. E.g. <em>user_mail</em>, <em>register_pending_approval_admin</em>') . '<br />' . $this->t('For example, to queue all mails sent by the User module, enter: <em>user_*</em> above, to just queue password recovery emails enter: <em>user_password_reset</em>'),
+      '#default_value' => $config->get('queue_mail_keys'),
+    ];
+
+    // Get a list of modules that implement hook_mail.
+    $mail_modules = $this->moduleHandler->getImplementations('mail');
+
+    $rows = [];
+    foreach ($mail_modules as $name) {
+      $row = [];
+      $row[] = $name;
+      $row[] = $name . '_*';
+      $rows[] = $row;
+    }
+    $headers = [
+      $this->t('Module name'),
+      $this->t('Mail ID prefix'),
+    ];
+
+    $form['tables'] = [
+      '#type' => 'table',
+      '#caption' => $this->t('The following modules send emails. The contents of the second column can be used in the options above to queue the sending of those emails.'),
+      '#header' => $headers,
+      '#rows' => $rows,
+      '#empty' => $this->t('No tables available.'),
+    ];
+
+    $form['advanced'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Advanced settings'),
+      '#collapsed' => TRUE,
+    ];
+
+    $options = [];
+    for ($i = 5; $i <= 240; $i += 5) {
+      $options[$i] = $this->formatPlural($i, '1 second', '@count seconds');
+    }
+
+    $form['advanced']['queue_mail_queue_time'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Queue processing time (max)'),
+      '#description' => $this->t('How much time in seconds to allow queue mail to send emails for on cron. Warning if you set a very high limit your cron run could timeout and never complete.'),
+      '#options' => $options,
+      '#default_value' => $config->get('queue_mail_queue_time'),
+    ];
+
+    $form['advanced']['threshold'] = [
+      '#type' => 'number',
+      '#min' => 0,
+      '#title' => $this->t('Queue retry threshold'),
+      '#default_value' => $config->get('threshold'),
+      '#description' => $this->t('How many times to retry before deleting an item.'),
+    ];
+
+    $form['advanced']['requeue_interval'] = [
+      '#type' => 'number',
+      '#min' => 0,
+      '#title' => $this->t('Requeue interval'),
+      '#default_value' => $config->get('requeue_interval'),
+      '#field_suffix' => $this->t('seconds'),
+      '#description' => $this->t('How long to wait in seconds before retrying failed emails.'),
+    ];
+
+    $form['advanced']['queue_mail_queue_wait_time'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Wait time per item'),
+      '#description' => $this->t('How long to wait in seconds in between the processing of each item. This value can not be bigger than "Queue processing time" value.'),
+      '#step' => 1,
+      '#min' => 0,
+      '#field_suffix' => $this->t('seconds'),
+      '#default_value' => $config->get('queue_mail_queue_wait_time') ?: 0,
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $queue_mail_queue_time = $form_state->getValue('queue_mail_queue_time');
+    $queue_mail_queue_wait_time = $form_state->getValue('queue_mail_queue_wait_time');
+    if ($queue_mail_queue_time < $queue_mail_queue_wait_time) {
+      $form_state->setErrorByName('queue_mail_queue_wait_time', $this->t('"Wait time per item" value can not be bigger than "Queue processing time" value.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->config('queue_mail.settings')
+      ->set('queue_mail_keys', $form_state->getValue('queue_mail_keys'))
+      ->set('queue_mail_queue_time', $form_state->getValue('queue_mail_queue_time'))
+      ->set('threshold', $form_state->getValue('threshold'))
+      ->set('requeue_interval', $form_state->getValue('requeue_interval'))
+      ->set('queue_mail_queue_wait_time', $form_state->getValue('queue_mail_queue_wait_time'))
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/web/modules/queue_mail/src/Plugin/QueueWorker/SendMailQueueWorker.php b/web/modules/queue_mail/src/Plugin/QueueWorker/SendMailQueueWorker.php
new file mode 100644
index 0000000000..7fd80d2c32
--- /dev/null
+++ b/web/modules/queue_mail/src/Plugin/QueueWorker/SendMailQueueWorker.php
@@ -0,0 +1,296 @@
+<?php
+
+namespace Drupal\queue_mail\Plugin\QueueWorker;
+
+use Drupal\Core\Queue\QueueWorkerBase;
+use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\Core\Theme\ThemeInitializationInterface;
+use Drupal\Core\Mail\MailManagerInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+
+/**
+ * Sends emails form queue.
+ *
+ * @QueueWorker(
+ *   id = "queue_mail",
+ *   title = @Translation("Queue mail worker"),
+ *   cron = {"time" = 60}
+ * )
+ */
+class SendMailQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * Theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface
+   */
+  protected $themeManager;
+
+  /**
+   * Theme initialization.
+   *
+   * @var \Drupal\Core\Theme\ThemeInitializationInterface
+   */
+  protected $themeInitialization;
+
+  /**
+   * Active theme.
+   *
+   * @var \Drupal\Core\Theme\ActiveTheme
+   */
+  protected $activeTheme;
+
+  /**
+   * Mail manager.
+   *
+   * @var \Drupal\Core\Mail\MailManagerInterface
+   */
+  protected $mailManager;
+
+  /**
+   * Logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * Queue mail configuration.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected $config;
+
+  /**
+   * Queue for sending mails.
+   *
+   * @var \Drupal\Core\Queue\QueueInterface
+   */
+  protected $queue;
+
+  /**
+   * The module handler to invoke the alter hook.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('theme.manager'),
+      $container->get('theme.initialization'),
+      $container->get('plugin.manager.mail'),
+      $container->get('logger.factory'),
+      $container->get('config.factory'),
+      $container->get('queue'),
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(ThemeManagerInterface $theme_manager, ThemeInitializationInterface $theme_init, MailManagerInterface $mail_manager, LoggerChannelFactoryInterface $logger_factory, ConfigFactoryInterface $config_factory, ContainerAwareInterface $queue_factory, ModuleHandlerInterface $module_handler) {
+    $this->themeManager = $theme_manager;
+    $this->themeInitialization = $theme_init;
+    $this->activeTheme = $this->themeManager->getActiveTheme();
+    $this->mailManager = $mail_manager;
+    $this->logger = $logger_factory->get('mail');
+    $this->config = $config_factory->get('queue_mail.settings');
+    $this->queue = $queue_factory->get('queue_mail', TRUE);
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($message) {
+    $original_message = $message;
+    $interval = $this->config->get('requeue_interval');
+
+    // Prevent retrying until specified interval has elapsed.
+    if (isset($message['last_attempt']) && ($message['last_attempt'] + $interval) > time()) {
+      // Skip item.
+      throw new \RuntimeException(sprintf('Sending of mail "%s" is skipped in the mail queue due to requeue interval.', $message['id']));
+    }
+
+    // Invoke hook_queue_mail_send_alter() to allow all modules to alter the
+    // email before sending.
+    $this->moduleHandler->alter('queue_mail_send', $message);
+
+    // The caller requested sending. Sending was canceled by one or more
+    // hook_queue_mail_send_alter() implementations. We set 'result' to NULL,
+    // because FALSE indicates an error in sending.
+    if (empty($message['send'])) {
+      $message['result'] = NULL;
+
+      return $message;
+    }
+
+    // Retrieve the responsible implementation for this message.
+    $system = $this->mailManager->getInstance([
+      'module' => $message['module'],
+      'key' => $message['key'],
+    ]);
+
+    // Set theme that was used to generate mail body.
+    $this->setMailTheme($message);
+
+    // Set mail's language as active.
+    $current_langcode = $this->setMailLanguage($message);
+
+    try {
+      // Format the message body.
+      $message = $system->format($message);
+    }
+    finally {
+      // Revert the active theme, this is done inside a finally block so it is
+      // executed even if an exception is thrown during sending a mail.
+      $this->setActiveTheme($message);
+
+      // Revert the active language.
+      $this->setActiveLanguage($message, $current_langcode);
+    }
+
+    // Ensure that subject is plain text. By default translated and
+    // formatted strings are prepared for the HTML context and email
+    // subjects are plain strings.
+    if ($message['subject']) {
+      $message['subject'] = PlainTextOutput::renderFromHtml($message['subject']);
+    }
+    $message['result'] = $system->mail($message);
+
+    // Log errors.
+    if (!$message['result']) {
+      $this->logger->error('Error sending email (from %from to %to with reply-to %reply).', [
+        '%from' => $message['from'],
+        '%to' => $message['to'],
+        '%reply' => $message['reply-to'] ? $message['reply-to'] : $this->t('not set'),
+      ]);
+
+      $this->processRetryLimit($original_message);
+    }
+
+    $this->waitBetweenSending();
+
+    return $message;
+  }
+
+  /**
+   * Sets language from the message.
+   *
+   * @param array $message
+   *   Mail message.
+   *
+   * @return string
+   *   The negotiated language code.
+   */
+  protected function setMailLanguage(array $message) {
+    return $message['langcode'];
+  }
+
+  /**
+   * Restores back the negotiated language.
+   *
+   * @param array $message
+   *   Mail message.
+   * @param string $langcode
+   *   The negotiated language code.
+   */
+  protected function setActiveLanguage(array $message, $langcode) {
+  }
+
+  /**
+   * Set theme from the theme.
+   *
+   * @param array $message
+   *   Mail message.
+   */
+  protected function setMailTheme(array $message) {
+    if ($this->messageHasAnotherTheme($message)) {
+      $theme = $this->themeInitialization->initTheme($message['theme']);
+      $this->themeManager->setActiveTheme($theme);
+    }
+  }
+
+  /**
+   * Restore back theme that is used by default.
+   *
+   * @param array $message
+   *   Mail message.
+   */
+  protected function setActiveTheme(array $message) {
+    if ($this->messageHasAnotherTheme($message)) {
+      $this->themeManager->setActiveTheme($this->activeTheme);
+    }
+  }
+
+  /**
+   * Checks if message has been generated using another theme.
+   *
+   * @param array $message
+   *   Mail message.
+   *
+   * @return bool
+   *   TRUE if message has theme that is not an active theme, FALSE otherwise.
+   */
+  protected function messageHasAnotherTheme(array $message) {
+    return !empty($message['theme']) && $message['theme'] != $this->activeTheme->getName();
+  }
+
+  /**
+   * Wait between items processing.
+   *
+   * Wait if "Wait time per item" configuration is enabled.
+   */
+  protected function waitBetweenSending() {
+    if ($wait_time = $this->config->get('queue_mail_queue_wait_time')) {
+      sleep($wait_time);
+    }
+  }
+
+  /**
+   * Retry limit handler.
+   *
+   * Counts number of attempts and removes mails from queue after
+   * reaching threshold.
+   *
+   * @param array $original_message
+   *   Original message.
+   */
+  protected function processRetryLimit(array $original_message) {
+    $original_message['last_attempt'] = time();
+
+    if (!isset($original_message['fail_count'])) {
+      $original_message['fail_count'] = 0;
+    }
+    $original_message['fail_count']++;
+
+    $threshold = $this->config->get('threshold');
+
+    // Add back to the queue with an updated fail count.
+    if ($original_message['fail_count'] < $threshold) {
+      $this->queue->createItem($original_message);
+    }
+    else {
+      $this->logger->error('Attempt sending email (from %from to %to with reply-to %reply) exceeded retry threshold and was deleted.', [
+        '%from' => $original_message['from'],
+        '%to' => $original_message['to'],
+        '%reply' => $original_message['reply-to'] ? $original_message['reply-to'] : $this->t('not set'),
+      ]);
+    }
+  }
+
+}
diff --git a/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.info.yml b/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.info.yml
new file mode 100644
index 0000000000..13b88fac79
--- /dev/null
+++ b/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.info.yml
@@ -0,0 +1,14 @@
+name: Queue Mail Test
+type: module
+description: Module for use by the queue mail module tests.
+core_version_requirement: ^8 || ^9
+dependencies:
+  - queue_mail:queue_mail
+
+package: Testing
+hidden: true
+
+# Information added by Drupal.org packaging script on 2021-09-22
+version: '8.x-1.4'
+project: 'queue_mail'
+datestamp: 1632316351
diff --git a/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.module b/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.module
new file mode 100644
index 0000000000..6deaa61543
--- /dev/null
+++ b/web/modules/queue_mail/tests/modules/queue_mail_test/queue_mail_test.module
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Module for testing the Queue Mail module.
+ */
+
+use Drupal\queue_mail_test\Plugin\Mail\QueueMailTestMailCollector;
+
+/**
+ * Implements hook_mail().
+ */
+function queue_mail_test_mail($key, &$message, $params) {
+  $message['body'][] = $params['content'];
+  if ($key === 'skipped') {
+    $message['send'] = FALSE;
+  }
+}
+
+/**
+ * Implements hook_queue_mail_send_alter().
+ */
+function queue_mail_test_queue_mail_send_alter(&$message) {
+  if ($message['key'] === 'cancel_message') {
+    $message['send'] = FALSE;
+  }
+}
+
+/**
+ * Implements hook_mail_backend_info_alter().
+ */
+function queue_mail_test_mail_backend_info_alter(&$info) {
+  $info['test_mail_collector']['class'] = QueueMailTestMailCollector::class;
+}
diff --git a/web/modules/queue_mail/tests/modules/queue_mail_test/src/Plugin/Mail/QueueMailTestMailCollector.php b/web/modules/queue_mail/tests/modules/queue_mail_test/src/Plugin/Mail/QueueMailTestMailCollector.php
new file mode 100644
index 0000000000..0e8a82acdd
--- /dev/null
+++ b/web/modules/queue_mail/tests/modules/queue_mail_test/src/Plugin/Mail/QueueMailTestMailCollector.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\queue_mail_test\Plugin\Mail;
+
+use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
+
+/**
+ * QueueMailTestMailCollector class.
+ *
+ * Defines a mail backend that captures sent and formatted messages in the state
+ * system.
+ */
+class QueueMailTestMailCollector extends TestMailCollector {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function format(array $message) {
+    $message = parent::format($message);
+
+    $message['current_langcode'] = \Drupal::languageManager()
+      ->getCurrentLanguage()
+      ->getId();
+
+    return $message;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mail(array $message) {
+    $result = parent::mail($message);
+
+    if ($message['key'] == 'fail_message') {
+      $result = FALSE;
+    }
+
+    return $result;
+  }
+
+}
diff --git a/web/modules/queue_mail/tests/src/Functional/QueueMailConfigurationTest.php b/web/modules/queue_mail/tests/src/Functional/QueueMailConfigurationTest.php
new file mode 100644
index 0000000000..1940d28277
--- /dev/null
+++ b/web/modules/queue_mail/tests/src/Functional/QueueMailConfigurationTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\Tests\queue_mail\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests configuration of Queue mail module.
+ *
+ * @group queue_mail
+ */
+class QueueMailConfigurationTest extends BrowserTestBase {
+
+  /**
+   * Admin user.
+   *
+   * @var \Drupal\user\Entity\User|false
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  const CONFIGURATION_PATH = 'admin/config/system/queue_mail';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['queue_mail'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp():void {
+    parent::setUp();
+
+    $this->adminUser = $this->drupalCreateUser(['administer site configuration']);
+  }
+
+  /**
+   * Tests default settings on the settings form.
+   */
+  public function testDefaultConfiguration() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet(static::CONFIGURATION_PATH);
+
+    $default_values = [
+      'queue_mail_keys' => '',
+      'queue_mail_queue_time'  => 15,
+      'queue_mail_queue_wait_time' => 0,
+      'threshold' => 50,
+      'requeue_interval' => 10800,
+    ];
+
+    foreach ($default_values as $field => $value) {
+      $this->assertSession()->fieldValueEquals($field, $value);
+    }
+  }
+
+  /**
+   * Tests change of settings.
+   */
+  public function testChangeConfiguration() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet(static::CONFIGURATION_PATH);
+
+    $edit = [
+      'queue_mail_keys' => '*',
+      'queue_mail_queue_time'  => 60,
+      'queue_mail_queue_wait_time' => 15,
+      'threshold' => 100,
+      'requeue_interval' => 21600,
+    ];
+    $this->submitForm($edit, 'Save configuration');
+
+    $this->drupalGet(static::CONFIGURATION_PATH);
+    foreach ($edit as $field => $value) {
+      $this->assertSession()->fieldValueEquals($field, $value);
+    }
+  }
+
+  /**
+   * Tests "Wait time per item" setting validation.
+   */
+  public function testWaitTimePerItemValidation() {
+    $this->drupalLogin($this->adminUser);
+
+    $validation_text = '"Wait time per item" value can not be bigger than "Queue processing time" value.';
+
+    // "Wait time per item" value is bigger than "Queue processing time" value.
+    $edit = [
+      'queue_mail_queue_time'  => 30,
+      'queue_mail_queue_wait_time' => 35,
+    ];
+    $this->drupalGet(static::CONFIGURATION_PATH);
+    $this->submitForm($edit, 'Save configuration');
+    $this->assertSession()->responseContains($validation_text);
+
+    // "Wait time per item" value is less than "Queue processing time" value.
+    $edit = [
+      'queue_mail_queue_time'  => 30,
+      'queue_mail_queue_wait_time' => 25,
+    ];
+    $this->submitForm($edit, 'Save configuration');
+    $this->assertSession()->responseNotContains($validation_text);
+  }
+
+}
diff --git a/web/modules/queue_mail/tests/src/Functional/QueueMailFunctionalTest.php b/web/modules/queue_mail/tests/src/Functional/QueueMailFunctionalTest.php
new file mode 100644
index 0000000000..fdebf703ea
--- /dev/null
+++ b/web/modules/queue_mail/tests/src/Functional/QueueMailFunctionalTest.php
@@ -0,0 +1,251 @@
+<?php
+
+namespace Drupal\Tests\queue_mail\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Core\Test\AssertMailTrait;
+use Drupal\Tests\Traits\Core\CronRunTrait;
+
+/**
+ * Tests queue mail functionality.
+ *
+ * @group queue_mail
+ */
+class QueueMailFunctionalTest extends BrowserTestBase {
+
+  use AssertMailTrait;
+  use CronRunTrait;
+
+  /**
+   * The mail language code.
+   *
+   * @var string
+   */
+  protected $langcode;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = ['queue_mail', 'queue_mail_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp():void {
+    parent::setUp();
+    $this->langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
+  }
+
+  /**
+   * Prepares parameters of test mails.
+   */
+  protected function getMessageParams() {
+    return [
+      'content' => $this->randomMachineName(),
+    ];
+  }
+
+  /**
+   * Sets settings that all emails have to be queued for testing.
+   */
+  protected function setAllEmailsToBeQueued() {
+    // Set all emails to be queued and test.
+    \Drupal::configFactory()->getEditable('queue_mail.settings')
+      ->set('queue_mail_keys', '*')
+      ->save();
+  }
+
+  /**
+   * Test that if we're not queuing any emails that they get sent as normal.
+   */
+  public function testNonQueuedEmail() {
+    // Send an email and ensure it was sent immediately.
+    \Drupal::configFactory()->getEditable('queue_mail.settings')
+      ->set('queue_mail_keys', '')
+      ->save();
+    $this->sendEmailAndTest('basic', FALSE);
+  }
+
+  /**
+   * Test that if we are queuing emails, that they get queued.
+   */
+  public function testQueuedEmail() {
+    $this->setAllEmailsToBeQueued();
+    $this->sendEmailAndTest();
+  }
+
+  /**
+   * This tests the matching of mailkeys to be queued.
+   *
+   * For example, we test that a specific email from a module is queued, and
+   * that emails from another module are not queued.
+   */
+  public function testQueuedEmailKeyMatching() {
+    // Set only some emails to be queued and test.
+    \Drupal::configFactory()->getEditable('queue_mail.settings')
+      ->set('queue_mail_keys', 'queue_mail_test_queued')
+      ->save();
+    $this->sendEmailAndTest('queued', TRUE);
+    $this->sendEmailAndTest('not_queued', FALSE);
+
+    // And test the wildcard matching.
+    \Drupal::configFactory()->getEditable('queue_mail.settings')
+      ->set('queue_mail_keys', 'queue_mail_test_que*')
+      ->save();
+    $this->sendEmailAndTest('queued', TRUE);
+    $this->sendEmailAndTest('not_queued', FALSE);
+  }
+
+  /**
+   * Test that messages are not queued if the the "send" flag is FALSE.
+   */
+  public function testSkippedEmail() {
+    $this->setAllEmailsToBeQueued();
+
+    $this->assertEmpty($this->getMails(), 'Ensure that mail collector is empty.');
+
+    $this->sendQueueMailTest('skipped', 'info@example.com', $this->getMessageParams());
+    $this->assertEmpty($this->getMails(), 'Emails has not been sent.');
+
+    $queue = _queue_mail_get_queue();
+    $this->assertEquals(0, $queue->numberOfItems(), 'Email has not been added to the mail queue.');
+
+    $this->cronRun();
+    $this->assertEmpty($this->getMails(), 'Emails has not been sent after cron run.');
+  }
+
+  /**
+   * Send an email and ensure it is queued or sent immediately.
+   *
+   * @param string $mail_key
+   *   The key of the email to send.
+   * @param bool $should_be_queued
+   *   Pass in TRUE to test if the email was queued, FALSE to test that it
+   *   wasn't queued.
+   */
+  public function sendEmailAndTest($mail_key = 'basic', $should_be_queued = TRUE) {
+    $queue = _queue_mail_get_queue();
+    // Parameters before testing.
+    $queue_count_before = $queue->numberOfItems();
+    $email_count_before = count($this->getMails());
+    $content = $this->randomMachineName();
+
+    // Send test email.
+    $message = $this->sendQueueMailTest($mail_key, 'info@example.com', ['content' => $content]);
+
+    $queue_count_after = $queue->numberOfItems();
+    $email_count_after = count($this->getMails());
+
+    // Now do the desired assertions.
+    if ($should_be_queued === TRUE) {
+      $this->assertEquals(1, $queue_count_after - $queue_count_before, 'Email is queued.');
+      $this->assertEquals(0, $email_count_after - $email_count_before, 'Queued email is not sent immediately.');
+
+      // Now run cron and see if our email gets sent.
+      $queue_count_before = $queue->numberOfItems();
+      $email_count_before = count($this->getMails());
+      $this->cronRun();
+      $this->assertMailString('body', $content, 1);
+      $queue_count_after = $queue->numberOfItems();
+      $email_count_after = count($this->getMails());
+      $this->assertEquals(-1, $queue_count_after - $queue_count_before, 'Email is sent from the queue.');
+      $this->assertEquals(1, $email_count_after - $email_count_before, 'Queued email is sent on cron.');
+      $this->assertMail('current_langcode', $this->langcode, 'The mail language was respected');
+      $this->assertTrue($message['queued'], 'Message has been added to the queue.');
+    }
+    elseif ($should_be_queued === FALSE) {
+      $this->assertEquals(0, $queue_count_after - $queue_count_before, 'Email is not queued.');
+      $this->assertEquals(1, $email_count_after - $email_count_before, 'Email is sent immediately.');
+      $this->assertMailString('body', $content, 1);
+      $this->assertFalse($message['queued'], 'Message has not been added to the queue.');
+    }
+  }
+
+  /**
+   * Test that message sending may be canceled.
+   *
+   * @see queue_mail_test_queue_mail_send_alter()
+   */
+  public function testCancelMessage() {
+    $this->setAllEmailsToBeQueued();
+
+    $queue = _queue_mail_get_queue();
+    $queue_count_init = $queue->numberOfItems();
+
+    // Send test mails.
+    $params = $this->getMessageParams();
+    $this->sendQueueMailTest('cancel_message', 'cancel@example.com', $params);
+    $this->sendQueueMailTest('send_message', 'send@example.com', $params);
+
+    // Ensures that both mails in the queue.
+    $queue_count_after_adding = $queue->numberOfItems();
+    $this->assertEquals(2, $queue_count_after_adding - $queue_count_init, 'Emails are queued.');
+
+    $this->cronRun();
+
+    // Checks that queue has been emptied.
+    $queue_count_after_sending = $queue->numberOfItems();
+    $this->assertEquals($queue_count_init, $queue_count_after_sending, 'Emails have been removed from queue');
+
+    // Ensures that just one emails has been sent from two created.
+    $email_count_after_sending = count($this->getMails());
+    $this->assertEquals(1, $email_count_after_sending, 'One email is sent only.');
+    $this->assertMailString('key', 'send_message', 1);
+  }
+
+  /**
+   * Wraps send mail function.
+   *
+   * @param string $key
+   *   A key to identify the email sent.
+   * @param string $to
+   *   The email address or addresses where the message will be sent to.
+   * @param array $params
+   *   (optional) Parameters to build the email.
+   *
+   * @return array
+   *   The $message array structure containing all details of the message.
+   */
+  protected function sendQueueMailTest($key, $to, array $params = []) {
+    return \Drupal::service('plugin.manager.mail')
+      ->mail('queue_mail_test', $key, $to, $this->langcode, $params);
+  }
+
+  /**
+   * Test that message sending may be failed.
+   */
+  public function testFailMessage() {
+    $this->setAllEmailsToBeQueued();
+
+    $queue = _queue_mail_get_queue();
+    $queue_count_init = $queue->numberOfItems();
+
+    $params = $this->getMessageParams();
+    // Send message that won't be send and will be re-queued.
+    $this->sendQueueMailTest('fail_message', 'fail@example.com', $params);
+    $this->cronRun();
+    $queue_count_after_adding = $queue->numberOfItems();
+    // Ensures that "fail_message" hasn't been sent.
+    $this->assertEquals(1, $queue_count_after_adding - $queue_count_init, 'Mail sending has been failed. Message is in the queue.');
+
+    // Send normal message.
+    $this->sendQueueMailTest('send_message', 'send@example.com', $params);
+    $queue_count_after_adding = $queue->numberOfItems();
+    // Ensures that there are two messages in the queue - "fail_message" and
+    // "send_message".
+    $this->assertEquals(2, $queue_count_after_adding - $queue_count_init, 'Mail sending has been failed. Message is in the queue.');
+    $this->cronRun();
+
+    // Ensures that one mail has been processed and one is still in the queue.
+    $queue_count_after_adding = $queue->numberOfItems();
+    $this->assertEquals(1, $queue_count_after_adding - $queue_count_init, 'One message has been processed.');
+  }
+
+}
-- 
GitLab