Skip to content

Commit

Permalink
feat(email-plugin): Support custom EmailGenerators and EmailSenders
Browse files Browse the repository at this point in the history
Co-authored-by: Will Milne <[email protected]>
  • Loading branch information
WilliamMilne and ibm-digital-commerce authored Feb 18, 2021
1 parent 5d9ecaa commit 3e20624
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 8 deletions.
18 changes: 13 additions & 5 deletions packages/email-plugin/src/email-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import fs from 'fs-extra';
import { deserializeAttachments } from './attachment-utils';
import { isDevModeOptions } from './common';
import { loggerCtx } from './constants';
import { EmailSender } from './email-sender';
import { DefaultEmailSender } from './email-sender';
import { HandlebarsMjmlGenerator } from './handlebars-mjml-generator';
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
Expand All @@ -17,15 +23,17 @@ import { EmailPluginOptions, EmailTransportOptions, IntermediateEmailDetails } f
export class EmailProcessor {
protected templateLoader: TemplateLoader;
protected emailSender: EmailSender;
protected generator: HandlebarsMjmlGenerator;
protected generator: EmailGenerator;
protected transport: EmailTransportOptions;

constructor(protected options: EmailPluginOptions) {}

async init() {
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) {
await this.generator.onInit.call(this.generator, this.options);
}
Expand Down
10 changes: 8 additions & 2 deletions packages/email-plugin/src/email-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ import { Stream } from 'stream';
import { format } from 'util';

import { loggerCtx } from './constants';
import { EmailDetails, EmailTransportOptions, SendmailTransportOptions, SMTPTransportOptions } from './types';
import {
EmailDetails,
EmailSender,
EmailTransportOptions,
SendmailTransportOptions,
SMTPTransportOptions,
} from './types';

export type StreamTransportInfo = {
envelope: {
Expand All @@ -26,7 +32,7 @@ export type StreamTransportInfo = {
/**
* Uses the configured transport to send the generated email.
*/
export class EmailSender {
export class DefaultEmailSender implements EmailSender {
private _smtpTransport: Mail | undefined;
private _sendMailTransport: Mail | undefined;

Expand Down
36 changes: 35 additions & 1 deletion packages/email-plugin/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { orderConfirmationHandler } from './default-email-handlers';
import { EmailEventHandler } from './event-handler';
import { EmailEventListener } from './event-listener';
import { EmailPlugin } from './plugin';
import { EmailPluginOptions } from './types';
import { EmailDetails, EmailPluginOptions, EmailSender, EmailTransportOptions } from './types';

describe('EmailPlugin', () => {
let eventBus: EventBus;
Expand Down Expand Up @@ -624,8 +624,42 @@ describe('EmailPlugin', () => {
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" <[email protected]>')
.setRecipient(() => '[email protected]')
.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('[email protected]');
expect(send.mock.calls[0][0].from).toBe('"test from" <[email protected]>');
expect(onSend).toHaveBeenCalledTimes(0);
});
});
});

class FakeCustomSender implements EmailSender {
send: (email: EmailDetails<'unserialized'>, options: EmailTransportOptions) => void;
}

const pause = () => new Promise(resolve => setTimeout(resolve, 100));

class MockEvent extends VendureEvent {
Expand Down
16 changes: 16 additions & 0 deletions packages/email-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ export interface EmailPluginOptions {
* email.
*/
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;
}

/**
Expand Down Expand Up @@ -262,6 +274,10 @@ export interface TestingTransportOptions {
onSend: (details: EmailDetails) => void;
}

export interface EmailSender {
send: (email: EmailDetails, options: EmailTransportOptions) => void;
}

/**
* @description
* An EmailGenerator generates the subject and body details of an email.
Expand Down

0 comments on commit 3e20624

Please sign in to comment.