Browse Source

feat(email-plugin): Add support for computed email subject (#2863)

21_Reinite 1 year ago
parent
commit
e546f24095

+ 13 - 3
packages/email-plugin/src/handler/event-handler.ts

@@ -14,6 +14,7 @@ import {
     LoadDataFn,
     SetAttachmentsFn,
     SetOptionalAddressFieldsFn,
+    SetSubjectFn,
     SetTemplateVarsFn,
 } from '../types';
 
@@ -135,6 +136,7 @@ import {
 export class EmailEventHandler<T extends string = string, Event extends EventWithContext = EventWithContext> {
     private setRecipientFn: (event: Event) => string;
     private setLanguageCodeFn: (event: Event) => LanguageCode | undefined;
+    private setSubjectFn?: SetSubjectFn<Event>;
     private setTemplateVarsFn: SetTemplateVarsFn<Event>;
     private setAttachmentsFn?: SetAttachmentsFn<Event>;
     private setOptionalAddressFieldsFn?: SetOptionalAddressFieldsFn<Event>;
@@ -214,8 +216,12 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
      * Sets the default subject of the email. The subject string may use Handlebars variables defined by the
      * setTemplateVars() method.
      */
-    setSubject(defaultSubject: string): EmailEventHandler<T, Event> {
-        this.defaultSubject = defaultSubject;
+    setSubject(defaultSubject: string | SetSubjectFn<Event>): EmailEventHandler<T, Event> {
+        if (typeof defaultSubject === 'string') {
+            this.defaultSubject = defaultSubject;
+        } else {
+            this.setSubjectFn = defaultSubject;
+        }
         return this;
     }
 
@@ -370,7 +376,11 @@ export class EmailEventHandler<T extends string = string, Event extends EventWit
         const { ctx } = event;
         const languageCode = this.setLanguageCodeFn?.(event) || ctx.languageCode;
         const configuration = this.getBestConfiguration(ctx.channel.code, languageCode);
-        const subject = configuration ? configuration.subject : this.defaultSubject;
+        const subject = configuration
+            ? configuration.subject
+            : this.setSubjectFn
+              ? await this.setSubjectFn(event, ctx, injector)
+              : this.defaultSubject;
         if (subject == null) {
             throw new Error(
                 `No subject field has been defined. ` +

+ 51 - 2
packages/email-plugin/src/plugin.spec.ts

@@ -28,7 +28,9 @@ import { EmailSender } from './sender/email-sender';
 import { EmailEventHandler } from './handler/event-handler';
 import { EmailEventListener } from './event-listener';
 import { EmailPlugin } from './plugin';
-import { EmailDetails, EmailPluginOptions, EmailTransportOptions } from './types';
+import { EmailDetails, EmailPluginOptions, EmailTransportOptions, LoadTemplateInput } from './types';
+import { TemplateLoader } from './template-loader/template-loader';
+import fs from 'fs-extra';
 
 describe('EmailPlugin', () => {
     let eventBus: EventBus;
@@ -913,6 +915,53 @@ describe('EmailPlugin', () => {
             expect(transport.type).toBe('testing');
         });
     });
+
+    describe('Dynamic subject handling', () => {
+        it('With string', 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' }));
+
+            await initPluginWithHandlers([handler]);
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe('Hello');
+            expect(onSend.mock.calls[0][0].recipient).toBe('test@test.com');
+            expect(onSend.mock.calls[0][0].from).toBe('"test from" <noreply@test.com>');
+        });
+        it('With callback function', 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(async (_e, _ctx, _i) => {
+                    const service = _i.get(MockService)
+                    const mockData = await service.someAsyncMethod()
+                    return `Hello from ${mockData} and {{ subjectVar }}`;
+                })
+                .setTemplateVars(event => ({ subjectVar: 'foo' }));
+
+            await initPluginWithHandlers([handler]);
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(onSend.mock.calls[0][0].subject).toBe('Hello from loaded data and foo');
+            expect(onSend.mock.calls[0][0].recipient).toBe('test@test.com');
+            expect(onSend.mock.calls[0][0].from).toBe('"test from" <noreply@test.com>');
+        });
+    })
 });
 
 class FakeCustomSender implements EmailSender {
@@ -931,4 +980,4 @@ class MockService {
     someAsyncMethod() {
         return Promise.resolve('loaded data');
     }
-}
+}

+ 9 - 1
packages/email-plugin/src/types.ts

@@ -4,9 +4,9 @@ import { Injector, RequestContext, SerializedRequestContext, VendureEvent } from
 import { Attachment } from 'nodemailer/lib/mailer';
 import SESTransport from 'nodemailer/lib/ses-transport';
 import SMTPTransport from 'nodemailer/lib/smtp-transport';
-import { EmailEventHandler } from './handler/event-handler';
 
 import { EmailGenerator } from './generator/email-generator';
+import { EmailEventHandler } from './handler/event-handler';
 import { EmailSender } from './sender/email-sender';
 import { TemplateLoader } from './template-loader/template-loader';
 
@@ -387,6 +387,14 @@ export type SetTemplateVarsFn<Event> = (
  */
 export type SetAttachmentsFn<Event> = (event: Event) => EmailAttachment[] | Promise<EmailAttachment[]>;
 
+/**
+ * @description
+ * A function used to define the subject to be sent with the email.
+ * @docsCategory core plugins/EmailPlugin
+ * @docsPage Email Plugin Types
+ */
+export type SetSubjectFn<Event> = (event: Event, ctx: RequestContext, injector: Injector) => string | Promise<string>;
+
 /**
  * @description
  * Optional address-related fields for sending the email.