Browse Source

feat(email-plugin): Support custom EmailGenerators and EmailSenders

Co-authored-by: Will Milne <Will.Milne@ibm.com>
William Milne 4 years ago
parent
commit
3e2062444c

+ 13 - 5
packages/email-plugin/src/email-processor.ts

@@ -4,10 +4,16 @@ import fs from 'fs-extra';
 import { deserializeAttachments } from './attachment-utils';
 import { deserializeAttachments } from './attachment-utils';
 import { isDevModeOptions } from './common';
 import { isDevModeOptions } from './common';
 import { loggerCtx } from './constants';
 import { loggerCtx } from './constants';
-import { EmailSender } from './email-sender';
+import { DefaultEmailSender } from './email-sender';
 import { HandlebarsMjmlGenerator } from './handlebars-mjml-generator';
 import { HandlebarsMjmlGenerator } from './handlebars-mjml-generator';
 import { TemplateLoader } from './template-loader';
 import { TemplateLoader } from './template-loader';
-import { EmailPluginOptions, EmailTransportOptions, IntermediateEmailDetails } from './types';
+import {
+    EmailGenerator,
+    EmailPluginOptions,
+    EmailSender,
+    EmailTransportOptions,
+    IntermediateEmailDetails,
+} from './types';
 
 
 /**
 /**
  * This class combines the template loading, generation, and email sending - the actual "work" of
  * This class combines the template loading, generation, and email sending - the actual "work" of
@@ -17,15 +23,17 @@ import { EmailPluginOptions, EmailTransportOptions, IntermediateEmailDetails } f
 export class EmailProcessor {
 export class EmailProcessor {
     protected templateLoader: TemplateLoader;
     protected templateLoader: TemplateLoader;
     protected emailSender: EmailSender;
     protected emailSender: EmailSender;
-    protected generator: HandlebarsMjmlGenerator;
+    protected generator: EmailGenerator;
     protected transport: EmailTransportOptions;
     protected transport: EmailTransportOptions;
 
 
     constructor(protected options: EmailPluginOptions) {}
     constructor(protected options: EmailPluginOptions) {}
 
 
     async init() {
     async init() {
         this.templateLoader = new TemplateLoader(this.options.templatePath);
         this.templateLoader = new TemplateLoader(this.options.templatePath);
-        this.emailSender = new EmailSender();
-        this.generator = new HandlebarsMjmlGenerator();
+        this.emailSender = this.options.emailSender ? this.options.emailSender : new DefaultEmailSender();
+        this.generator = this.options.emailGenerator
+            ? this.options.emailGenerator
+            : new HandlebarsMjmlGenerator();
         if (this.generator.onInit) {
         if (this.generator.onInit) {
             await this.generator.onInit.call(this.generator, this.options);
             await this.generator.onInit.call(this.generator, this.options);
         }
         }

+ 8 - 2
packages/email-plugin/src/email-sender.ts

@@ -12,7 +12,13 @@ import { Stream } from 'stream';
 import { format } from 'util';
 import { format } from 'util';
 
 
 import { loggerCtx } from './constants';
 import { loggerCtx } from './constants';
-import { EmailDetails, EmailTransportOptions, SendmailTransportOptions, SMTPTransportOptions } from './types';
+import {
+    EmailDetails,
+    EmailSender,
+    EmailTransportOptions,
+    SendmailTransportOptions,
+    SMTPTransportOptions,
+} from './types';
 
 
 export type StreamTransportInfo = {
 export type StreamTransportInfo = {
     envelope: {
     envelope: {
@@ -26,7 +32,7 @@ export type StreamTransportInfo = {
 /**
 /**
  * Uses the configured transport to send the generated email.
  * Uses the configured transport to send the generated email.
  */
  */
-export class EmailSender {
+export class DefaultEmailSender implements EmailSender {
     private _smtpTransport: Mail | undefined;
     private _smtpTransport: Mail | undefined;
     private _sendMailTransport: Mail | undefined;
     private _sendMailTransport: Mail | undefined;
 
 

+ 35 - 1
packages/email-plugin/src/plugin.spec.ts

@@ -20,7 +20,7 @@ import { orderConfirmationHandler } from './default-email-handlers';
 import { EmailEventHandler } from './event-handler';
 import { EmailEventHandler } from './event-handler';
 import { EmailEventListener } from './event-listener';
 import { EmailEventListener } from './event-listener';
 import { EmailPlugin } from './plugin';
 import { EmailPlugin } from './plugin';
-import { EmailPluginOptions } from './types';
+import { EmailDetails, EmailPluginOptions, EmailSender, EmailTransportOptions } from './types';
 
 
 describe('EmailPlugin', () => {
 describe('EmailPlugin', () => {
     let eventBus: EventBus;
     let eventBus: EventBus;
@@ -624,8 +624,42 @@ describe('EmailPlugin', () => {
             expect(testingLogger.errorSpy.mock.calls[0][0]).toContain(`something went horribly wrong!`);
             expect(testingLogger.errorSpy.mock.calls[0][0]).toContain(`something went horribly wrong!`);
         });
         });
     });
     });
+
+    describe('custom sender', () => {
+        it('should allow a custom sender to be utilized', async () => {
+            const ctx = RequestContext.deserialize({
+                _channel: { code: DEFAULT_CHANNEL_CODE },
+                _languageCode: LanguageCode.en,
+            } as any);
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello')
+                .setTemplateVars(event => ({ subjectVar: 'foo' }));
+
+            const fakeSender = new FakeCustomSender();
+            const send = jest.fn();
+            fakeSender.send = send;
+
+            await initPluginWithHandlers([handler], {
+                emailSender: fakeSender,
+            });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(send.mock.calls[0][0].subject).toBe('Hello');
+            expect(send.mock.calls[0][0].recipient).toBe('test@test.com');
+            expect(send.mock.calls[0][0].from).toBe('"test from" <noreply@test.com>');
+            expect(onSend).toHaveBeenCalledTimes(0);
+        });
+    });
 });
 });
 
 
+class FakeCustomSender implements EmailSender {
+    send: (email: EmailDetails<'unserialized'>, options: EmailTransportOptions) => void;
+}
+
 const pause = () => new Promise(resolve => setTimeout(resolve, 100));
 const pause = () => new Promise(resolve => setTimeout(resolve, 100));
 
 
 class MockEvent extends VendureEvent {
 class MockEvent extends VendureEvent {

+ 16 - 0
packages/email-plugin/src/types.ts

@@ -59,6 +59,18 @@ export interface EmailPluginOptions {
      * email.
      * email.
      */
      */
     globalTemplateVars?: { [key: string]: any };
     globalTemplateVars?: { [key: string]: any };
+    /**
+     * @description
+     * An optional allowed EmailSender, used to allow custom implementations of the send functionality
+     * while still utilizing the existing emailPlugin functionality.
+     */
+    emailSender?: EmailSender;
+    /**
+     * @description
+     * An optional allowed EmailGenerator, used to allow custom email generation functionality to
+     * better match with custom email sending functionality.
+     */
+    emailGenerator?: EmailGenerator;
 }
 }
 
 
 /**
 /**
@@ -262,6 +274,10 @@ export interface TestingTransportOptions {
     onSend: (details: EmailDetails) => void;
     onSend: (details: EmailDetails) => void;
 }
 }
 
 
+export interface EmailSender {
+    send: (email: EmailDetails, options: EmailTransportOptions) => void;
+}
+
 /**
 /**
  * @description
  * @description
  * An EmailGenerator generates the subject and body details of an email.
  * An EmailGenerator generates the subject and body details of an email.