From e4175e742e8431a20b4e60be61fe6c9a27efc982 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 20 Feb 2024 15:04:04 +0100 Subject: [PATCH] feat(email-plugin): Publish EmailSendEvent after send attempted --- .../email-event-handler-with-async-data.md | 2 +- .../email-plugin/email-event-handler.md | 4 +- .../email-plugin/email-generator.md | 4 +- .../email-plugin/email-plugin-options.md | 30 +++++++-------- .../email-plugin/email-plugin-types.md | 18 ++++----- .../email-plugin/email-send-event.md | 38 +++++++++++++++++++ .../core-plugins/email-plugin/email-sender.md | 6 +-- .../core-plugins/email-plugin/email-utils.md | 4 +- .../core-plugins/email-plugin/index.md | 30 +++++++-------- .../email-plugin/template-loader.md | 4 +- .../email-plugin/transport-options.md | 16 ++++---- .../configuration/entity-duplicator.md | 26 ++++++------- .../custom-fields/custom-field-config.md | 2 +- .../typescript-api/custom-fields/index.md | 2 +- .../fulfillment-transition-data.md | 2 +- .../reference/typescript-api/job-queue/job.md | 2 +- .../payment/refund-transition-data.md | 2 +- .../typescript-api/request/allow-decorator.md | 2 +- .../typescript-api/request/request-context.md | 2 +- packages/email-plugin/index.ts | 5 ++- packages/email-plugin/src/email-processor.ts | 12 ++++-- packages/email-plugin/src/email-send-event.ts | 23 +++++++++++ packages/email-plugin/src/plugin.ts | 8 +--- 23 files changed, 154 insertions(+), 90 deletions(-) create mode 100644 docs/docs/reference/core-plugins/email-plugin/email-send-event.md create mode 100644 packages/email-plugin/src/email-send-event.ts diff --git a/docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md b/docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md index 930356ba57..0791f30ccb 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md +++ b/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 - + Identical to the EmailEventHandler but with a `data` property added to the `event` based on the result of the `.loadData()` function. diff --git a/docs/docs/reference/core-plugins/email-plugin/email-event-handler.md b/docs/docs/reference/core-plugins/email-plugin/email-event-handler.md index 9d1c9aa001..90ca488d06 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-event-handler.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-event-handler.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailEventHandler - + 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 }), diff --git a/docs/docs/reference/core-plugins/email-plugin/email-generator.md b/docs/docs/reference/core-plugins/email-plugin/email-generator.md index d9a93f3a00..6487a0509a 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-generator.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-generator.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailGenerator - + An EmailGenerator generates the subject and body details of an email. @@ -50,7 +50,7 @@ interpolated email text. ## HandlebarsMjmlGenerator - + Uses Handlebars (https://handlebarsjs.com/) to output MJML (https://mjml.io) which is then compiled down to responsive email HTML. diff --git a/docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md b/docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md index 1766811f24..311f266a6f 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-plugin-options.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailPluginOptions - + 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); handlers: Array>; globalTemplateVars?: { [key: string]: any }; @@ -38,43 +38,43 @@ interface EmailPluginOptions { -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 `/vendure/email/templates`. ### templateLoader TemplateLoader`} 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 `/vendure/email/templates` ### transport -EmailTransportOptions | (( injector?: Injector, ctx?: RequestContext, ) => EmailTransportOptions | Promise<EmailTransportOptions>)`} /> +EmailTransportOptions | (( injector?: Injector, ctx?: RequestContext, ) => EmailTransportOptions | Promise<EmailTransportOptions>)`} /> Configures how the emails are sent. ### handlers EmailEventHandler<string, any>>`} /> -An array of EmailEventHandlers which define which Vendure events will trigger +An array of EmailEventHandlers which define which Vendure events will trigger emails, and how those emails are generated. ### globalTemplateVars -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 EmailSender`} default="NodemailerEmailSender" /> -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 EmailGenerator`} default="HandlebarsMjmlGenerator" /> -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 - + Configuration for running the EmailPlugin in development mode. diff --git a/docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md b/docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md index e2ca1e56dd..50943f7b61 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EventWithContext - + A VendureEvent which also includes a `ctx` property containing the current RequestContext, which is used to determine the channel and language @@ -24,7 +24,7 @@ type EventWithContext = VendureEvent & { ctx: RequestContext } ## EventWithAsyncData - + A VendureEvent with a RequestContext and a `data` property which contains the value resolved from the EmailEventHandler`.loadData()` callback. @@ -36,7 +36,7 @@ type EventWithAsyncData = Event & { data: R } ## EmailDetails - + The final, generated email details to be sent. @@ -102,7 +102,7 @@ interface EmailDetailsEmailEventHandler. @@ -116,7 +116,7 @@ type LoadDataFn = (context: { ## EmailAttachment - + 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 & { path?: string } ## SetTemplateVarsFn - + A function used to define template variables available to email templates. See EmailEventHandler.setTemplateVars(). @@ -145,7 +145,7 @@ type SetTemplateVarsFn = ( ## SetAttachmentsFn - + 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) => EmailAttachment[] | Promise + Optional address-related fields for sending the email. @@ -194,7 +194,7 @@ An email address that will appear on the _Reply-To:_ field ## SetOptionalAddressFieldsFn - + A function used to set the OptionalAddressFields. diff --git a/docs/docs/reference/core-plugins/email-plugin/email-send-event.md b/docs/docs/reference/core-plugins/email-plugin/email-send-event.md new file mode 100644 index 0000000000..166ac88851 --- /dev/null +++ b/docs/docs/reference/core-plugins/email-plugin/email-send-event.md @@ -0,0 +1,38 @@ +--- +title: "EmailSendEvent" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + +## EmailSendEvent + + + +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: VendureEvent + + + +
+ +### constructor + +RequestContext, details: EmailDetails, success: boolean, error?: Error) => EmailSendEvent`} /> + + + + +
diff --git a/docs/docs/reference/core-plugins/email-plugin/email-sender.md b/docs/docs/reference/core-plugins/email-plugin/email-sender.md index d2f1958b44..e314dc2e07 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-sender.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-sender.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailSender - + 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 - + Uses the configured transport to send the generated email. diff --git a/docs/docs/reference/core-plugins/email-plugin/email-utils.md b/docs/docs/reference/core-plugins/email-plugin/email-utils.md index 324ae359e8..e7e0b82bb2 100644 --- a/docs/docs/reference/core-plugins/email-plugin/email-utils.md +++ b/docs/docs/reference/core-plugins/email-plugin/email-utils.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## transformOrderLineAssetUrls - + 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 - + Ensures that the ShippingLines are hydrated so that we can use the `shippingMethod.name` property in the email template. diff --git a/docs/docs/reference/core-plugins/email-plugin/index.md b/docs/docs/reference/core-plugins/email-plugin/index.md index 02d0c45ce1..17b4b13464 100644 --- a/docs/docs/reference/core-plugins/email-plugin/index.md +++ b/docs/docs/reference/core-plugins/email-plugin/index.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailPlugin - + 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 EventBus) that allows plugins to subscribe to events. The EmailPlugin is configured with EmailEventHandlers 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 EmailEventHandler docs for more details. +You can also create your own handler and register them with the plugin - see the EmailEventHandler 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 + 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 - + Loads email templates from the local file system. This is the default loader used by the EmailPlugin. diff --git a/docs/docs/reference/core-plugins/email-plugin/transport-options.md b/docs/docs/reference/core-plugins/email-plugin/transport-options.md index e080425102..d4fc4ff736 100644 --- a/docs/docs/reference/core-plugins/email-plugin/transport-options.md +++ b/docs/docs/reference/core-plugins/email-plugin/transport-options.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## EmailTransportOptions - + A union of all the possible transport options for sending emails. @@ -27,7 +27,7 @@ type EmailTransportOptions = | SMTPTransportOptions ## SMTPTransportOptions - + The SMTP transport options of [Nodemailer](https://nodemailer.com/smtp/) @@ -61,7 +61,7 @@ the SMTP server. ## SESTransportOptions - + 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 - + Uses the local Sendmail program to send the email. @@ -156,7 +156,7 @@ interface SendmailTransportOptions { ## FileTransportOptions - + Outputs the email as an HTML file for development purposes. @@ -192,7 +192,7 @@ interface FileTransportOptions { ## NoopTransportOptions - + 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 EmailSender which does not require transport options. @@ -217,7 +217,7 @@ interface NoopTransportOptions { ## TestingTransportOptions - + Forwards the raw GeneratedEmailContext object to a provided callback, for use in testing. diff --git a/docs/docs/reference/typescript-api/configuration/entity-duplicator.md b/docs/docs/reference/typescript-api/configuration/entity-duplicator.md index cd26019426..339589e905 100644 --- a/docs/docs/reference/typescript-api/configuration/entity-duplicator.md +++ b/docs/docs/reference/typescript-api/configuration/entity-duplicator.md @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; -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 extends ConfigurableOperationDef { constructor(config: EntityDuplicatorConfig) - duplicate(input: { - ctx: RequestContext; - entityName: string; - id: ID; - args: ConfigArg[]; + duplicate(input: { + ctx: RequestContext; + entityName: string; + id: ID; + args: ConfigArg[]; }) => Promise; } ``` @@ -123,7 +123,7 @@ class EntityDuplicator extends ConfigurableOp ### duplicate -RequestContext; entityName: string; id: ID; args: ConfigArg[]; }) => Promise<VendureEntity>`} /> +RequestContext; entityName: string; id: ID; args: ConfigArg[]; }) => Promise<VendureEntity>`} /> @@ -138,11 +138,11 @@ class EntityDuplicator extends ConfigurableOp A function which performs the duplication of an entity. ```ts title="Signature" -type DuplicateEntityFn = (input: { - ctx: RequestContext; - entityName: string; - id: ID; - args: ConfigArgValues; +type DuplicateEntityFn = (input: { + ctx: RequestContext; + entityName: string; + id: ID; + args: ConfigArgValues; }) => Promise ``` @@ -170,7 +170,7 @@ interface EntityDuplicatorConfig extends ConfigurableOpera Permission | string> | Permission | 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 diff --git a/docs/docs/reference/typescript-api/custom-fields/custom-field-config.md b/docs/docs/reference/typescript-api/custom-fields/custom-field-config.md index c523ee4790..f982df939f 100644 --- a/docs/docs/reference/typescript-api/custom-fields/custom-field-config.md +++ b/docs/docs/reference/typescript-api/custom-fields/custom-field-config.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## CustomFieldConfig - + An object used to configure a custom field. diff --git a/docs/docs/reference/typescript-api/custom-fields/index.md b/docs/docs/reference/typescript-api/custom-fields/index.md index ba91866ec8..481a1612ca 100644 --- a/docs/docs/reference/typescript-api/custom-fields/index.md +++ b/docs/docs/reference/typescript-api/custom-fields/index.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## CustomFields - + Most entities can have additional fields added to them by defining an array of CustomFieldConfig objects on against the corresponding key. diff --git a/docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md b/docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md index 6618d6e94e..e2cce043a8 100644 --- a/docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md +++ b/docs/docs/reference/typescript-api/fulfillment/fulfillment-transition-data.md @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; -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 { diff --git a/docs/docs/reference/typescript-api/job-queue/job.md b/docs/docs/reference/typescript-api/job-queue/job.md index ff2f584be4..39401089ae 100644 --- a/docs/docs/reference/typescript-api/job-queue/job.md +++ b/docs/docs/reference/typescript-api/job-queue/job.md @@ -166,7 +166,7 @@ destroyed before the job has been completed. JobEventType, listener: JobEventListener<T>) => `} /> -Used to register event handlers for job events +Used to register event handler for job events ### off JobEventType, listener: JobEventListener<T>) => `} /> diff --git a/docs/docs/reference/typescript-api/payment/refund-transition-data.md b/docs/docs/reference/typescript-api/payment/refund-transition-data.md index e5aa10e88d..5b4dde41b3 100644 --- a/docs/docs/reference/typescript-api/payment/refund-transition-data.md +++ b/docs/docs/reference/typescript-api/payment/refund-transition-data.md @@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; -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 { diff --git a/docs/docs/reference/typescript-api/request/allow-decorator.md b/docs/docs/reference/typescript-api/request/allow-decorator.md index 47b150071a..f47629ca83 100644 --- a/docs/docs/reference/typescript-api/request/allow-decorator.md +++ b/docs/docs/reference/typescript-api/request/allow-decorator.md @@ -18,7 +18,7 @@ operation, using one or more Ctx 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 TransactionalConnection relies on the diff --git a/packages/email-plugin/index.ts b/packages/email-plugin/index.ts index 244dbd35e2..a541404e80 100644 --- a/packages/email-plugin/index.ts +++ b/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'; diff --git a/packages/email-plugin/src/email-processor.ts b/packages/email-plugin/src/email-processor.ts index dcb948b37c..a9db06e849 100644 --- a/packages/email-plugin/src/email-processor.ts +++ b/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; } } diff --git a/packages/email-plugin/src/email-send-event.ts b/packages/email-plugin/src/email-send-event.ts new file mode 100644 index 0000000000..fe35beabb9 --- /dev/null +++ b/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(); + } +} diff --git a/packages/email-plugin/src/plugin.ts b/packages/email-plugin/src/plugin.ts index a920aaef98..1573b884fa 100644 --- a/packages/email-plugin/src/plugin.ts +++ b/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();