Răsfoiți Sursa

feat(email-plugin): Publish EmailSendEvent after send attempted

Michael Bromley 1 an în urmă
părinte
comite
e4175e742e
23 a modificat fișierele cu 154 adăugiri și 90 ștergeri
  1. 1 1
      docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md
  2. 2 2
      docs/docs/reference/core-plugins/email-plugin/email-event-handler.md
  3. 2 2
      docs/docs/reference/core-plugins/email-plugin/email-generator.md
  4. 15 15
      docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md
  5. 9 9
      docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md
  6. 38 0
      docs/docs/reference/core-plugins/email-plugin/email-send-event.md
  7. 3 3
      docs/docs/reference/core-plugins/email-plugin/email-sender.md
  8. 2 2
      docs/docs/reference/core-plugins/email-plugin/email-utils.md
  9. 15 15
      docs/docs/reference/core-plugins/email-plugin/index.md
  10. 2 2
      docs/docs/reference/core-plugins/email-plugin/template-loader.md
  11. 8 8
      docs/docs/reference/core-plugins/email-plugin/transport-options.md
  12. 13 13
      docs/docs/reference/typescript-api/configuration/entity-duplicator.md
  13. 1 1
      docs/docs/reference/typescript-api/custom-fields/custom-field-config.md
  14. 1 1
      docs/docs/reference/typescript-api/custom-fields/index.md
  15. 1 1
      docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md
  16. 1 1
      docs/docs/reference/typescript-api/job-queue/job.md
  17. 1 1
      docs/docs/reference/typescript-api/payment/refund-transition-data.md
  18. 1 1
      docs/docs/reference/typescript-api/request/allow-decorator.md
  19. 1 1
      docs/docs/reference/typescript-api/request/request-context.md
  20. 3 2
      packages/email-plugin/index.ts
  21. 9 3
      packages/email-plugin/src/email-processor.ts
  22. 23 0
      packages/email-plugin/src/email-send-event.ts
  23. 2 6
      packages/email-plugin/src/plugin.ts

+ 1 - 1
docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailEventHandlerWithAsyncData
 
-<GenerationInfo sourceFile="packages/email-plugin/src/event-handler.ts" sourceLine="438" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="438" packageName="@vendure/email-plugin" />
 
 Identical to the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a> but with a `data` property added to the `event` based on the result
 of the `.loadData()` function.

+ 2 - 2
docs/docs/reference/core-plugins/email-plugin/email-event-handler.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailEventHandler
 
-<GenerationInfo sourceFile="packages/email-plugin/src/event-handler.ts" sourceLine="131" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="131" packageName="@vendure/email-plugin" />
 
 The EmailEventHandler defines how the EmailPlugin will respond to a given event.
 
@@ -114,7 +114,7 @@ const config: VendureConfig = {
   // Add an instance of the plugin to the plugins array
   plugins: [
     EmailPlugin.init({
-      handlers: [...defaultEmailHandlers, quoteRequestedHandler],
+      handler: [...defaultEmailHandlers, quoteRequestedHandler],
       templatePath: path.join(__dirname, 'vendure/email/templates'),
       // ... etc
     }),

+ 2 - 2
docs/docs/reference/core-plugins/email-plugin/email-generator.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailGenerator
 
-<GenerationInfo sourceFile="packages/email-plugin/src/email-generator.ts" sourceLine="13" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/generator/email-generator.ts" sourceLine="13" packageName="@vendure/email-plugin" />
 
 An EmailGenerator generates the subject and body details of an email.
 
@@ -50,7 +50,7 @@ interpolated email text.
 
 ## HandlebarsMjmlGenerator
 
-<GenerationInfo sourceFile="packages/email-plugin/src/handlebars-mjml-generator.ts" sourceLine="23" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/generator/handlebars-mjml-generator.ts" sourceLine="17" packageName="@vendure/email-plugin" />
 
 Uses Handlebars (https://handlebarsjs.com/) to output MJML (https://mjml.io) which is then
 compiled down to responsive email HTML.

+ 15 - 15
docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailPluginOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="40" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="41" packageName="@vendure/email-plugin" />
 
 Configuration for the EmailPlugin.
 
@@ -19,11 +19,11 @@ Configuration for the EmailPlugin.
 interface EmailPluginOptions {
     templatePath?: string;
     templateLoader?: TemplateLoader;
-    transport:
-        | EmailTransportOptions
-        | ((
-              injector?: Injector,
-              ctx?: RequestContext,
+    transport:
+        | EmailTransportOptions
+        | ((
+              injector?: Injector,
+              ctx?: RequestContext,
           ) => EmailTransportOptions | Promise<EmailTransportOptions>);
     handlers: Array<EmailEventHandler<string, any>>;
     globalTemplateVars?: { [key: string]: any };
@@ -38,43 +38,43 @@ interface EmailPluginOptions {
 
 <MemberInfo kind="property" type={`string`}   />
 
-The path to the location of the email templates. In a default Vendure installation,
+The path to the location of the email templates. In a default Vendure installation,
 the templates are installed to `<project root>/vendure/email/templates`.
 ### templateLoader
 
 <MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/template-loader#templateloader'>TemplateLoader</a>`}  since="2.0.0"  />
 
-An optional TemplateLoader which can be used to load templates from a custom location or async service.
+An optional TemplateLoader which can be used to load templates from a custom location or async service.
 The default uses the FileBasedTemplateLoader which loads templates from `<project root>/vendure/email/templates`
 ### transport
 
-<MemberInfo kind="property" type={`| <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>
         | ((
               injector?: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>,
               ctx?: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>,
           ) =&#62; <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | Promise&#60;<a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>&#62;)`}   />
+<MemberInfo kind="property" type={`| <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>         | ((               injector?: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>,               ctx?: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>,           ) =&#62; <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | Promise&#60;<a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>&#62;)`}   />
 
 Configures how the emails are sent.
 ### handlers
 
 <MemberInfo kind="property" type={`Array&#60;<a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>&#60;string, any&#62;&#62;`}   />
 
-An array of <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>s which define which Vendure events will trigger
+An array of <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>s which define which Vendure events will trigger
 emails, and how those emails are generated.
 ### globalTemplateVars
 
 <MemberInfo kind="property" type={`{ [key: string]: any }`}   />
 
-An object containing variables which are made available to all templates. For example,
-the storefront URL could be defined here and then used in the "email address verification"
+An object containing variables which are made available to all templates. For example,
+the storefront URL could be defined here and then used in the "email address verification"
 email.
 ### emailSender
 
 <MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/email-sender#emailsender'>EmailSender</a>`} default="<a href='/reference/core-plugins/email-plugin/email-sender#nodemaileremailsender'>NodemailerEmailSender</a>"   />
 
-An optional allowed EmailSender, used to allow custom implementations of the send functionality
+An optional allowed EmailSender, used to allow custom implementations of the send functionality
 while still utilizing the existing emailPlugin functionality.
 ### emailGenerator
 
 <MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/email-generator#emailgenerator'>EmailGenerator</a>`} default="<a href='/reference/core-plugins/email-plugin/email-generator#handlebarsmjmlgenerator'>HandlebarsMjmlGenerator</a>"   />
 
-An optional allowed EmailGenerator, used to allow custom email generation functionality to
+An optional allowed EmailGenerator, used to allow custom email generation functionality to
 better match with custom email sending functionality.
 
 
@@ -83,7 +83,7 @@ better match with custom email sending functionality.
 
 ## EmailPluginDevModeOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="110" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="111" packageName="@vendure/email-plugin" />
 
 Configuration for running the EmailPlugin in development mode.
 

+ 9 - 9
docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EventWithContext
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="21" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="22" packageName="@vendure/email-plugin" />
 
 A VendureEvent which also includes a `ctx` property containing the current
 <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, which is used to determine the channel and language
@@ -24,7 +24,7 @@ type EventWithContext = VendureEvent & { ctx: RequestContext }
 
 ## EventWithAsyncData
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="31" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="32" packageName="@vendure/email-plugin" />
 
 A VendureEvent with a <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> and a `data` property which contains the
 value resolved from the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>`.loadData()` callback.
@@ -36,7 +36,7 @@ type EventWithAsyncData<Event extends EventWithContext, R> = Event & { data: R }
 
 ## EmailDetails
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="248" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="249" packageName="@vendure/email-plugin" />
 
 The final, generated email details to be sent.
 
@@ -102,7 +102,7 @@ interface EmailDetails<Type extends 'serialized' | 'unserialized' = 'unserialize
 
 ## LoadDataFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="282" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="283" packageName="@vendure/email-plugin" />
 
 A function used to load async data for use by an <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>.
 
@@ -116,7 +116,7 @@ type LoadDataFn<Event extends EventWithContext, R> = (context: {
 
 ## EmailAttachment
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="301" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="302" packageName="@vendure/email-plugin" />
 
 An object defining a file attachment for an email. Based on the object described
 [here in the Nodemailer docs](https://nodemailer.com/message/attachments/), but
@@ -130,7 +130,7 @@ type EmailAttachment = Omit<Attachment, 'raw'> & { path?: string }
 
 ## SetTemplateVarsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="414" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="374" packageName="@vendure/email-plugin" />
 
 A function used to define template variables available to email templates.
 See <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>.setTemplateVars().
@@ -145,7 +145,7 @@ type SetTemplateVarsFn<Event> = (
 
 ## SetAttachmentsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="428" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="388" packageName="@vendure/email-plugin" />
 
 A function used to define attachments to be sent with the email.
 See https://nodemailer.com/message/attachments/ for more information about
@@ -158,7 +158,7 @@ type SetAttachmentsFn<Event> = (event: Event) => EmailAttachment[] | Promise<Ema
 
 ## OptionalAddressFields
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="438" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="398" packageName="@vendure/email-plugin" since="1.1.0" />
 
 Optional address-related fields for sending the email.
 
@@ -194,7 +194,7 @@ An email address that will appear on the _Reply-To:_ field
 
 ## SetOptionalAddressFieldsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="464" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="424" packageName="@vendure/email-plugin" since="1.1.0" />
 
 A function used to set the <a href='/reference/core-plugins/email-plugin/email-plugin-types#optionaladdressfields'>OptionalAddressFields</a>.
 

+ 38 - 0
docs/docs/reference/core-plugins/email-plugin/email-send-event.md

@@ -0,0 +1,38 @@
+---
+title: "EmailSendEvent"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## EmailSendEvent
+
+<GenerationInfo sourceFile="packages/email-plugin/src/email-send-event.ts" sourceLine="14" packageName="@vendure/email-plugin" since="2.2.0" />
+
+This event is fired when an email sending attempt has been made. If the sending was successful,
+the `success` property will be `true`, and if not, the `error` property will contain the error
+which occurred.
+
+```ts title="Signature"
+class EmailSendEvent extends VendureEvent {
+    constructor(ctx: RequestContext, details: EmailDetails, success: boolean, error?: Error)
+}
+```
+* Extends: <code><a href='/reference/typescript-api/events/vendure-event#vendureevent'>VendureEvent</a></code>
+
+
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, details: <a href='/reference/core-plugins/email-plugin/email-plugin-types#emaildetails'>EmailDetails</a>, success: boolean, error?: Error) => EmailSendEvent`}   />
+
+
+
+
+</div>

+ 3 - 3
docs/docs/reference/core-plugins/email-plugin/email-sender.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailSender
 
-<GenerationInfo sourceFile="packages/email-plugin/src/email-sender.ts" sourceLine="45" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/sender/email-sender.ts" sourceLine="45" packageName="@vendure/email-plugin" />
 
 An EmailSender is responsible for sending the email, e.g. via an SMTP connection
 or using some other mail-sending API. By default, the EmailPlugin uses the
@@ -40,7 +40,7 @@ const config: VendureConfig = {
   // ...
   plugins: [
     EmailPlugin.init({
-       // ... template, handlers config omitted
+       // ... template, handler config omitted
       transport: { type: 'none' },
        emailSender: new SendgridEmailSender(),
     }),
@@ -71,7 +71,7 @@ interface EmailSender extends InjectableStrategy {
 
 ## NodemailerEmailSender
 
-<GenerationInfo sourceFile="packages/email-plugin/src/nodemailer-email-sender.ts" sourceLine="38" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/sender/nodemailer-email-sender.ts" sourceLine="39" packageName="@vendure/email-plugin" />
 
 Uses the configured transport to send the generated email.
 

+ 2 - 2
docs/docs/reference/core-plugins/email-plugin/email-utils.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## transformOrderLineAssetUrls
 
-<GenerationInfo sourceFile="packages/email-plugin/src/default-email-handlers.ts" sourceLine="101" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/default-email-handlers.ts" sourceLine="101" packageName="@vendure/email-plugin" />
 
 Applies the configured `AssetStorageStrategy.toAbsoluteUrl()` function to each of the
 OrderLine's `featuredAsset.preview` properties, so that they can be correctly displayed
@@ -42,7 +42,7 @@ Parameters
 
 ## hydrateShippingLines
 
-<GenerationInfo sourceFile="packages/email-plugin/src/default-email-handlers.ts" sourceLine="122" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/default-email-handlers.ts" sourceLine="122" packageName="@vendure/email-plugin" />
 
 Ensures that the ShippingLines are hydrated so that we can use the
 `shippingMethod.name` property in the email template.

+ 15 - 15
docs/docs/reference/core-plugins/email-plugin/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailPlugin
 
-<GenerationInfo sourceFile="packages/email-plugin/src/plugin.ts" sourceLine="276" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/plugin.ts" sourceLine="272" packageName="@vendure/email-plugin" />
 
 The EmailPlugin creates and sends transactional emails based on Vendure events. By default, it uses an [MJML](https://mjml.io/)-based
 email generator to generate the email body and [Nodemailer](https://nodemailer.com/about/) to send the emails.
@@ -20,13 +20,13 @@ email generator to generate the email body and [Nodemailer](https://nodemailer.c
 Vendure has an internal events system (see <a href='/reference/typescript-api/events/event-bus#eventbus'>EventBus</a>) that allows plugins to subscribe to events. The EmailPlugin is configured with <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>s
 that listen for a specific event and when it is published, the handler defines which template to use to generate the resulting email.
 
-The plugin comes with a set of default handlers for the following events:
+The plugin comes with a set of default handler for the following events:
 - Order confirmation
 - New customer email address verification
 - Password reset request
 - Email address change request
 
-You can also create your own handlers and register them with the plugin - see the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a> docs for more details.
+You can also create your own handler and register them with the plugin - see the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a> docs for more details.
 
 ## Installation
 
@@ -45,7 +45,7 @@ const config: VendureConfig = {
   // Add an instance of the plugin to the plugins array
   plugins: [
     EmailPlugin.init({
-      handlers: defaultEmailHandlers,
+      handler: defaultEmailHandlers,
       templatePath: path.join(__dirname, 'static/email/templates'),
       transport: {
         type: 'smtp',
@@ -110,21 +110,21 @@ The following helper functions are available for use in email templates:
 * `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`
 * `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
 
-## Extending the default email handlers
+## Extending the default email handler
 
-The `defaultEmailHandlers` array defines the default handlers such as for handling new account registration, order confirmation, password reset
+The `defaultEmailHandlers` array defines the default handler such as for handling new account registration, order confirmation, password reset
 etc. These defaults can be extended by adding custom templates for languages other than the default, or even completely new types of emails
 which respond to any of the available [VendureEvents](/reference/typescript-api/events/).
 
-A good way to learn how to create your own email handlers is to take a look at the
-[source code of the default handlers](https://github.com/vendure-ecommerce/vendure/blob/master/packages/email-plugin/src/default-email-handlers.ts).
-New handlers are defined in exactly the same way.
+A good way to learn how to create your own email handler is to take a look at the
+[source code of the default handler](https://github.com/vendure-ecommerce/vendure/blob/master/packages/email-plugin/src/default-email-handlers.ts).
+New handler are defined in exactly the same way.
 
-It is also possible to modify the default handlers:
+It is also possible to modify the default handler:
 
 ```ts
 // Rather than importing `defaultEmailHandlers`, you can
-// import the handlers individually
+// import the handler individually
 import {
   orderConfirmationHandler,
   emailVerificationHandler,
@@ -151,10 +151,10 @@ const myPasswordResetHandler = passwordResetHandler
     customer: event.data.customer,
   }));
 
-// Then you pass the handlers to the EmailPlugin init method
+// Then you pass the handler to the EmailPlugin init method
 // individually
 EmailPlugin.init({
-  handlers: [
+  handler: [
     myOrderConfirmationHandler,
     myPasswordResetHandler,
     emailVerificationHandler,
@@ -179,7 +179,7 @@ import { MyTransportService } from './transport.services.ts';
 const config: VendureConfig = {
   plugins: [
     EmailPlugin.init({
-      handlers: defaultEmailHandlers,
+      handler: defaultEmailHandlers,
       templatePath: path.join(__dirname, 'static/email/templates'),
       transport: (injector, ctx) => {
         if (ctx) {
@@ -207,7 +207,7 @@ file transport (See <a href='/reference/core-plugins/email-plugin/transport-opti
 EmailPlugin.init({
   devMode: true,
   route: 'mailbox',
-  handlers: defaultEmailHandlers,
+  handler: defaultEmailHandlers,
   templatePath: path.join(__dirname, 'vendure/email/templates'),
   outputPath: path.join(__dirname, 'test-emails'),
 })

+ 2 - 2
docs/docs/reference/core-plugins/email-plugin/template-loader.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## TemplateLoader
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="392" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/template-loader/template-loader.ts" sourceLine="32" packageName="@vendure/email-plugin" />
 
 Loads email templates based on the given request context, type and template name
 and return the template as a string.
@@ -62,7 +62,7 @@ This method is only called during initialization, i.e. during server startup.
 
 ## FileBasedTemplateLoader
 
-<GenerationInfo sourceFile="packages/email-plugin/src/template-loader.ts" sourceLine="15" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/template-loader/file-based-template-loader.ts" sourceLine="17" packageName="@vendure/email-plugin" />
 
 Loads email templates from the local file system. This is the default
 loader used by the EmailPlugin.

+ 8 - 8
docs/docs/reference/core-plugins/email-plugin/transport-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="131" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="132" packageName="@vendure/email-plugin" />
 
 A union of all the possible transport options for sending emails.
 
@@ -27,7 +27,7 @@ type EmailTransportOptions = | SMTPTransportOptions
 
 ## SMTPTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="146" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="147" packageName="@vendure/email-plugin" />
 
 The SMTP transport options of [Nodemailer](https://nodemailer.com/smtp/)
 
@@ -61,7 +61,7 @@ the SMTP server.
 
 ## SESTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="195" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="196" packageName="@vendure/email-plugin" />
 
 The SES transport options of [Nodemailer](https://nodemailer.com/transports/ses//)
 
@@ -85,7 +85,7 @@ See [Nodemailers's SES docs](https://nodemailer.com/transports/ses/) for more de
   // Add an instance of the plugin to the plugins array
   plugins: [
     EmailPlugin.init({
-      handlers: defaultEmailHandlers,
+      handler: defaultEmailHandlers,
       templatePath: path.join(__dirname, 'static/email/templates'),
       transport: {
         type: 'ses',
@@ -120,7 +120,7 @@ interface SESTransportOptions extends SESTransport.Options {
 
 ## SendmailTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="206" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="207" packageName="@vendure/email-plugin" />
 
 Uses the local Sendmail program to send the email.
 
@@ -156,7 +156,7 @@ interface SendmailTransportOptions {
 
 ## FileTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="221" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="222" packageName="@vendure/email-plugin" />
 
 Outputs the email as an HTML file for development purposes.
 
@@ -192,7 +192,7 @@ interface FileTransportOptions {
 
 ## NoopTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="237" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="238" packageName="@vendure/email-plugin" />
 
 Does nothing with the generated email. Intended for use in testing where we don't care about the email transport,
 or when using a custom <a href='/reference/core-plugins/email-plugin/email-sender#emailsender'>EmailSender</a> which does not require transport options.
@@ -217,7 +217,7 @@ interface NoopTransportOptions {
 
 ## TestingTransportOptions
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="266" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="267" packageName="@vendure/email-plugin" />
 
 Forwards the raw GeneratedEmailContext object to a provided callback, for use in testing.
 

+ 13 - 13
docs/docs/reference/typescript-api/configuration/entity-duplicator.md

@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <GenerationInfo sourceFile="packages/core/src/config/entity/entity-duplicator.ts" sourceLine="158" packageName="@vendure/core" since="2.2.0" />
 
-An EntityDuplicator is used to define the logic for duplicating entities when the `duplicateEntity` mutation is called.
+An EntityDuplicator is used to define the logic for duplicating entities when the `duplicateEntity` mutation is called.
 This allows you to add support for duplication of both core and custom entities.
 
 *Example*
@@ -102,11 +102,11 @@ export const config: VendureConfig = {
 ```ts title="Signature"
 class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOperationDef<T> {
     constructor(config: EntityDuplicatorConfig<T>)
-    duplicate(input: {
-        ctx: RequestContext;
-        entityName: string;
-        id: ID;
-        args: ConfigArg[];
+    duplicate(input: {
+        ctx: RequestContext;
+        entityName: string;
+        id: ID;
+        args: ConfigArg[];
     }) => Promise<VendureEntity>;
 }
 ```
@@ -123,7 +123,7 @@ class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOp
 
 ### duplicate
 
-<MemberInfo kind="method" type={`(input: {         ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>;         entityName: string;         id: <a href='/reference/typescript-api/common/id#id'>ID</a>;         args: ConfigArg[];     }) => Promise&#60;<a href='/reference/typescript-api/entities/vendure-entity#vendureentity'>VendureEntity</a>&#62;`}   />
+<MemberInfo kind="method" type={`(input: {
         ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>;
         entityName: string;
         id: <a href='/reference/typescript-api/common/id#id'>ID</a>;
         args: ConfigArg[];
     }) => Promise&#60;<a href='/reference/typescript-api/entities/vendure-entity#vendureentity'>VendureEntity</a>&#62;`}   />
 
 
 
@@ -138,11 +138,11 @@ class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOp
 A function which performs the duplication of an entity.
 
 ```ts title="Signature"
-type DuplicateEntityFn<T extends ConfigArgs> = (input: {
-    ctx: RequestContext;
-    entityName: string;
-    id: ID;
-    args: ConfigArgValues<T>;
+type DuplicateEntityFn<T extends ConfigArgs> = (input: {
+    ctx: RequestContext;
+    entityName: string;
+    id: ID;
+    args: ConfigArgValues<T>;
 }) => Promise<VendureEntity>
 ```
 
@@ -170,7 +170,7 @@ interface EntityDuplicatorConfig<T extends ConfigArgs> extends ConfigurableOpera
 
 <MemberInfo kind="property" type={`Array&#60;<a href='/reference/typescript-api/common/permission#permission'>Permission</a> | string&#62; | <a href='/reference/typescript-api/common/permission#permission'>Permission</a> | string`}   />
 
-The permissions required in order to execute this duplicator. If an array is passed,
+The permissions required in order to execute this duplicator. If an array is passed,
 then the administrator must have at least one of the permissions in the array.
 ### forEntities
 

+ 1 - 1
docs/docs/reference/typescript-api/custom-fields/custom-field-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomFieldConfig
 
-<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="124" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="126" packageName="@vendure/core" />
 
 An object used to configure a custom field.
 

+ 1 - 1
docs/docs/reference/typescript-api/custom-fields/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomFields
 
-<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="159" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="161" packageName="@vendure/core" />
 
 Most entities can have additional fields added to them by defining an array of <a href='/reference/typescript-api/custom-fields/custom-field-config#customfieldconfig'>CustomFieldConfig</a>
 objects on against the corresponding key.

+ 1 - 1
docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md

@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <GenerationInfo sourceFile="packages/core/src/service/helpers/fulfillment-state-machine/fulfillment-state.ts" sourceLine="42" packageName="@vendure/core" />
 
-The data which is passed to the state transition handlers of the FulfillmentStateMachine.
+The data which is passed to the state transition handler of the FulfillmentStateMachine.
 
 ```ts title="Signature"
 interface FulfillmentTransitionData {

+ 1 - 1
docs/docs/reference/typescript-api/job-queue/job.md

@@ -166,7 +166,7 @@ destroyed before the job has been completed.
 
 <MemberInfo kind="method" type={`(eventType: <a href='/reference/typescript-api/job-queue/job#jobeventtype'>JobEventType</a>, listener: <a href='/reference/typescript-api/job-queue/job#jobeventlistener'>JobEventListener</a>&#60;T&#62;) => `}   />
 
-Used to register event handlers for job events
+Used to register event handler for job events
 ### off
 
 <MemberInfo kind="method" type={`(eventType: <a href='/reference/typescript-api/job-queue/job#jobeventtype'>JobEventType</a>, listener: <a href='/reference/typescript-api/job-queue/job#jobeventlistener'>JobEventListener</a>&#60;T&#62;) => `}   />

+ 1 - 1
docs/docs/reference/typescript-api/payment/refund-transition-data.md

@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <GenerationInfo sourceFile="packages/core/src/service/helpers/refund-state-machine/refund-state.ts" sourceLine="33" packageName="@vendure/core" />
 
-The data which is passed to the state transition handlers of the RefundStateMachine.
+The data which is passed to the state transition handler of the RefundStateMachine.
 
 ```ts title="Signature"
 interface RefundTransitionData {

+ 1 - 1
docs/docs/reference/typescript-api/request/allow-decorator.md

@@ -18,7 +18,7 @@ operation, using one or more <a href='/reference/typescript-api/common/permissio
 
 In a GraphQL context, it can be applied to top-level queries and mutations as well as field resolvers.
 
-For REST controllers, it can be applied to route handlers.
+For REST controllers, it can be applied to route handler.
 
 ## Allow and Sessions
 The `@Allow()` decorator is closely linked to the way Vendure manages sessions. For any operation or route that is decorated

+ 1 - 1
docs/docs/reference/typescript-api/request/request-context.md

@@ -17,7 +17,7 @@ The RequestContext holds information relevant to the current request, which may
 required at various points of the stack.
 
 It is a good practice to inject the RequestContext (using the <a href='/reference/typescript-api/request/ctx-decorator#ctx'>Ctx</a> decorator) into
-_all_ resolvers & REST handlers, and then pass it through to the service layer.
+_all_ resolvers & REST handler, and then pass it through to the service layer.
 
 This allows the service layer to access information about the current user, the active language,
 the active Channel, and so on. In addition, the <a href='/reference/typescript-api/data-access/transactional-connection#transactionalconnection'>TransactionalConnection</a> relies on the

+ 3 - 2
packages/email-plugin/index.ts

@@ -5,8 +5,9 @@ export * from './src/event-listener';
 export * from './src/generator/handlebars-mjml-generator';
 export * from './src/generator/noop-email-generator';
 export * from './src/plugin';
-export * from './src/template-loader';
+export * from './src/template-loader/template-loader';
+export * from './src/template-loader/file-based-template-loader';
 export * from './src/types';
+export * from './src/email-send-event';
 export * from './src/generator/email-generator';
 export * from './src/sender/email-sender';
-export { TemplateLoader } from './src/template-loader/template-loader';

+ 9 - 3
packages/email-plugin/src/email-processor.ts

@@ -1,11 +1,12 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { ModuleRef } from '@nestjs/core';
-import { Injector, Logger, RequestContext } from '@vendure/core';
+import { EventBus, Injector, Logger, RequestContext } from '@vendure/core';
 import fs from 'fs-extra';
 
 import { deserializeAttachments } from './attachment-utils';
 import { isDevModeOptions, resolveTransportSettings } from './common';
 import { EMAIL_PLUGIN_OPTIONS, loggerCtx } from './constants';
+import { EmailSendEvent } from './email-send-event';
 import { EmailGenerator } from './generator/email-generator';
 import { HandlebarsMjmlGenerator } from './generator/handlebars-mjml-generator';
 import { EmailSender } from './sender/email-sender';
@@ -30,6 +31,7 @@ export class EmailProcessor {
     constructor(
         @Inject(EMAIL_PLUGIN_OPTIONS) protected options: InitializedEmailPluginOptions,
         private moduleRef: ModuleRef,
+        private eventBus: EventBus,
     ) {}
 
     async init() {
@@ -50,8 +52,9 @@ export class EmailProcessor {
     }
 
     async process(data: IntermediateEmailDetails) {
+        const ctx = RequestContext.deserialize(data.ctx);
+        let emailDetails: EmailDetails = {} as any;
         try {
-            const ctx = RequestContext.deserialize(data.ctx);
             const bodySource = await this.options.templateLoader.loadTemplate(
                 new Injector(this.moduleRef),
                 ctx,
@@ -62,7 +65,7 @@ export class EmailProcessor {
                 },
             );
             const generated = this.generator.generate(data.from, data.subject, bodySource, data.templateVars);
-            const emailDetails: EmailDetails = {
+            emailDetails = {
                 ...generated,
                 recipient: data.recipient,
                 attachments: deserializeAttachments(data.attachments),
@@ -72,6 +75,7 @@ export class EmailProcessor {
             };
             const transportSettings = await this.getTransportSettings(ctx);
             await this.emailSender.send(emailDetails, transportSettings);
+            this.eventBus.publish(new EmailSendEvent(ctx, emailDetails, true));
             return true;
         } catch (err: unknown) {
             if (err instanceof Error) {
@@ -79,6 +83,8 @@ export class EmailProcessor {
             } else {
                 Logger.error(String(err), loggerCtx);
             }
+
+            this.eventBus.publish(new EmailSendEvent(ctx, emailDetails, false, err as Error));
             throw err;
         }
     }

+ 23 - 0
packages/email-plugin/src/email-send-event.ts

@@ -0,0 +1,23 @@
+import { RequestContext, VendureEvent } from '@vendure/core';
+
+import { EmailDetails } from './types';
+
+/**
+ * @description
+ * This event is fired when an email sending attempt has been made. If the sending was successful,
+ * the `success` property will be `true`, and if not, the `error` property will contain the error
+ * which occurred.
+ *
+ * @docsCategory core plugins/EmailPlugin
+ * @since 2.2.0
+ */
+export class EmailSendEvent extends VendureEvent {
+    constructor(
+        public readonly ctx: RequestContext,
+        public readonly details: EmailDetails,
+        public readonly success: boolean,
+        public readonly error?: Error,
+    ) {
+        super();
+    }
+}

+ 2 - 6
packages/email-plugin/src/plugin.ts

@@ -15,23 +15,19 @@ import {
     PluginCommonModule,
     ProcessContext,
     registerPluginStartupMessage,
-    RequestContext,
     Type,
-    UserInputError,
     VendurePlugin,
 } from '@vendure/core';
-import Module from 'module';
 
 import { isDevModeOptions, resolveTransportSettings } from './common';
 import { EMAIL_PLUGIN_OPTIONS, loggerCtx } from './constants';
 import { DevMailbox } from './dev-mailbox';
 import { EmailProcessor } from './email-processor';
 import { EmailEventHandler, EmailEventHandlerWithAsyncData } from './handler/event-handler';
-import { FileBasedTemplateLoader } from './template-loader';
+import { FileBasedTemplateLoader } from './template-loader/file-based-template-loader';
 import {
     EmailPluginDevModeOptions,
     EmailPluginOptions,
-    EmailTransportOptions,
     EventWithContext,
     InitializedEmailPluginOptions,
     IntermediateEmailDetails,
@@ -320,7 +316,7 @@ export class EmailPlugin implements OnApplicationBootstrap, OnApplicationShutdow
         if (!isDevModeOptions(this.options) && transport.type === 'testing') {
             // When running tests, we don't want to go through the JobQueue system,
             // so we just call the email sending logic directly.
-            this.testingProcessor = new EmailProcessor(this.options, this.moduleRef);
+            this.testingProcessor = new EmailProcessor(this.options, this.moduleRef, this.eventBus);
             await this.testingProcessor.init();
         } else {
             await this.emailProcessor.init();