Browse Source

feat(email-plugin): Added `from` field to the email config (#168)

* feat(email-plugin): Added `from` field to the email config

* test(email-plugin): Fix tests and raise error for missing  field
lsimone 6 years ago
parent
commit
09eb34e8fe

+ 16 - 2
packages/email-plugin/src/default-email-handlers.ts

@@ -1,13 +1,24 @@
 /* tslint:disable:no-non-null-assertion */
-import { AccountRegistrationEvent, IdentifierChangeRequestEvent, OrderStateTransitionEvent, PasswordResetEvent } from '@vendure/core';
+import {
+    AccountRegistrationEvent,
+    IdentifierChangeRequestEvent,
+    OrderStateTransitionEvent,
+    PasswordResetEvent,
+} from '@vendure/core';
 
 import { EmailEventListener } from './event-listener';
-import { mockAccountRegistrationEvent, mockEmailAddressChangeEvent, mockOrderStateTransitionEvent, mockPasswordResetEvent } from './mock-events';
+import {
+    mockAccountRegistrationEvent,
+    mockEmailAddressChangeEvent,
+    mockOrderStateTransitionEvent,
+    mockPasswordResetEvent,
+} from './mock-events';
 
 export const orderConfirmationHandler = new EmailEventListener('order-confirmation')
     .on(OrderStateTransitionEvent)
     .filter(event => event.toState === 'PaymentSettled' && !!event.order.customer)
     .setRecipient(event => event.order.customer!.emailAddress)
+    .setFrom(`{{ fromAddress }}`)
     .setSubject(`Order confirmation for #{{ order.code }}`)
     .setTemplateVars(event => ({ order: event.order }))
     .setMockEvent(mockOrderStateTransitionEvent);
@@ -15,6 +26,7 @@ export const orderConfirmationHandler = new EmailEventListener('order-confirmati
 export const emailVerificationHandler = new EmailEventListener('email-verification')
     .on(AccountRegistrationEvent)
     .setRecipient(event => event.user.identifier)
+    .setFrom(`{{ fromAddress }}`)
     .setSubject(`Please verify your email address`)
     .setTemplateVars(event => ({ user: event.user }))
     .setMockEvent(mockAccountRegistrationEvent);
@@ -22,6 +34,7 @@ export const emailVerificationHandler = new EmailEventListener('email-verificati
 export const passwordResetHandler = new EmailEventListener('password-reset')
     .on(PasswordResetEvent)
     .setRecipient(event => event.user.identifier)
+    .setFrom(`{{ fromAddress }}`)
     .setSubject(`Forgotten password reset`)
     .setTemplateVars(event => ({ user: event.user }))
     .setMockEvent(mockPasswordResetEvent);
@@ -29,6 +42,7 @@ export const passwordResetHandler = new EmailEventListener('password-reset')
 export const emailAddressChangeHandler = new EmailEventListener('email-address-change')
     .on(IdentifierChangeRequestEvent)
     .setRecipient(event => event.user.pendingIdentifier!)
+    .setFrom(`{{ fromAddress }}`)
     .setSubject(`Please verify your change of email address`)
     .setTemplateVars(event => ({ user: event.user }))
     .setMockEvent(mockEmailAddressChangeEvent);

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

@@ -66,6 +66,7 @@ export class EmailSender {
 
     private async sendMail(email: EmailDetails, transporter: Mail): Promise<any> {
         return transporter.sendMail({
+            from: email.from,
             to: email.recipient,
             subject: email.subject,
             html: email.body,
@@ -75,6 +76,7 @@ export class EmailSender {
     private async sendFileJson(email: EmailDetails, pathWithoutExt: string) {
         const output = {
             date: new Date().toLocaleString(),
+            from: email.from,
             recipient: email.recipient,
             subject: email.subject,
             body: email.body,

+ 22 - 4
packages/email-plugin/src/event-listener.ts

@@ -117,6 +117,7 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
     private filterFns: Array<(event: Event) => boolean> = [];
     private configurations: EmailTemplateConfig[] = [];
     private defaultSubject: string;
+    private from: string;
     private _mockEvent: Omit<Event, 'ctx'> | undefined;
 
     constructor(public listener: EmailEventListener<T>, public event: Type<Event>) {}
@@ -170,6 +171,16 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
         return this;
     }
 
+    /**
+     * @description
+     * Sets the default from field of the email. The from string may use Handlebars variables defined by the
+     * setTemplateVars() method.
+     */
+    setFrom(from: string): EmailEventHandler<T, Event> {
+        this.from = from;
+        return this;
+    }
+
     /**
      * @description
      * Add configuration for another template other than the default `"body.hbs"`. Use this method to define specific
@@ -189,7 +200,9 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
     handle(
         event: Event,
         globals: { [key: string]: any } = {},
-    ): { recipient: string; templateVars: any; subject: string; templateFile: string } | undefined {
+    ):
+        | { from: string; recipient: string; templateVars: any; subject: string; templateFile: string }
+        | undefined {
         for (const filterFn of this.filterFns) {
             if (!filterFn(event)) {
                 return;
@@ -198,9 +211,13 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
         if (!this.setRecipientFn) {
             throw new Error(
                 `No setRecipientFn has been defined. ` +
-                    `Remember to call ".setRecipient()" when setting up the EmailEventHandler for ${
-                        this.type
-                    }`,
+                    `Remember to call ".setRecipient()" when setting up the EmailEventHandler for ${this.type}`,
+            );
+        }
+        if (this.from === undefined) {
+            throw new Error(
+                `No from field has been defined. ` +
+                    `Remember to call ".setFrom()" when setting up the EmailEventHandler for ${this.type}`,
             );
         }
         const { ctx } = event;
@@ -209,6 +226,7 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
         const templateVars = this.setTemplateVarsFn ? this.setTemplateVarsFn(event, globals) : {};
         return {
             recipient,
+            from: this.from,
             templateVars: { ...globals, ...templateVars },
             subject: configuration ? configuration.subject : this.defaultSubject,
             templateFile: configuration ? configuration.templateFile : 'body.hbs',

+ 4 - 6
packages/email-plugin/src/handlebars-mjml-generator.ts

@@ -17,17 +17,15 @@ export class HandlebarsMjmlGenerator implements EmailGenerator {
         this.registerHelpers();
     }
 
-    generate(
-        subject: string,
-        template: string,
-        templateVars: any,
-    ) {
+    generate(from: string, subject: string, template: string, templateVars: any) {
+        const compiledFrom = Handlebars.compile(from);
         const compiledSubject = Handlebars.compile(subject);
         const compiledTemplate = Handlebars.compile(template);
+        const fromResult = compiledFrom(templateVars);
         const subjectResult = compiledSubject(templateVars);
         const mjml = compiledTemplate(templateVars);
         const body = mjml2html(mjml).html;
-        return { subject: subjectResult, body };
+        return { from: fromResult, subject: subjectResult, body };
     }
 
     private registerPartials(partialsPath: string) {

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

@@ -4,11 +4,7 @@ import { EmailGenerator } from './types';
  * Simply passes through the subject and template content without modification.
  */
 export class NoopEmailGenerator implements EmailGenerator {
-    generate(
-        subject: string,
-        body: string,
-        templateVars: any,
-    ) {
-        return { subject, body };
+    generate(from: string, subject: string, body: string, templateVars: any) {
+        return { from, subject, body };
     }
 }

+ 25 - 0
packages/email-plugin/src/plugin.spec.ts

@@ -55,6 +55,7 @@ describe('EmailPlugin', () => {
                 .on(MockEvent)
                 .filter(event => event.shouldSend === true)
                 .setRecipient(() => 'test@test.com')
+                .setFrom('"test from" <noreply@test.com>')
                 .setSubject('test subject');
 
             const module = await initPluginWithHandlers([handler]);
@@ -74,6 +75,7 @@ describe('EmailPlugin', () => {
                 .on(MockEvent)
                 .filter(event => event.shouldSend === true)
                 .filter(event => !!event.ctx.user)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('test subject');
 
@@ -99,6 +101,7 @@ describe('EmailPlugin', () => {
         it('interpolates subject', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello {{ subjectVar }}')
                 .setTemplateVars(event => ({ subjectVar: 'foo' }));
@@ -114,6 +117,7 @@ describe('EmailPlugin', () => {
         it('interpolates body', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello')
                 .setTemplateVars(event => ({ testVar: 'this is the test var' }));
@@ -129,6 +133,7 @@ describe('EmailPlugin', () => {
         it('interpolates globalTemplateVars', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello {{ globalVar }}');
 
@@ -142,9 +147,27 @@ describe('EmailPlugin', () => {
             await module.close();
         });
 
+        it('interpolates from', async () => {
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setFrom('"test from {{ globalVar }}" <noreply@test.com>')
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello');
+
+            const module = await initPluginWithHandlers([handler], {
+                globalTemplateVars: { globalVar: 'baz' },
+            });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].from).toBe('"test from baz" <noreply@test.com>');
+            await module.close();
+        });
+
         it('globalTemplateVars available in setTemplateVars method', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello {{ testVar }}')
                 .setTemplateVars((event, globals) => ({ testVar: globals.globalVar + ' quux' }));
@@ -162,6 +185,7 @@ describe('EmailPlugin', () => {
         it('setTemplateVars overrides globals', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello {{ name }}')
                 .setTemplateVars((event, globals) => ({ name: 'quux' }));
@@ -184,6 +208,7 @@ describe('EmailPlugin', () => {
         it('additional LanguageCode', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
                 .setSubject('Hello, {{ name }}!')
                 .setRecipient(() => 'test@test.com')
                 .setTemplateVars(() => ({ name: 'Test' }))

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

@@ -230,7 +230,12 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose {
             return;
         }
         const bodySource = await this.templateLoader.loadTemplate(type, result.templateFile);
-        const generated = await this.generator.generate(result.subject, bodySource, result.templateVars);
+        const generated = await this.generator.generate(
+            result.from,
+            result.subject,
+            bodySource,
+            result.templateVars,
+        );
         const emailDetails = { ...generated, recipient: result.recipient };
         await this.emailSender.send(emailDetails, this.transport);
     }

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

@@ -206,6 +206,7 @@ export interface NoopTransportOptions {
  * @docsPage Email Plugin Types
  */
 export interface EmailDetails {
+    from: string;
     recipient: string;
     subject: string;
     body: string;
@@ -244,6 +245,7 @@ export interface EmailGenerator<T extends string = any, E extends VendureEvent =
      * interpolated email text.
      */
     generate(
+        from: string,
         subject: string,
         body: string,
         templateVars: { [key: string]: any },