Skip to content

Commit

Permalink
feat(email-plugin): Support dynamic globalTemplateVars (#2950)
Browse files Browse the repository at this point in the history
Closes #2933
  • Loading branch information
mschipperheyn authored Jul 17, 2024
1 parent 9db77d7 commit cab67b6
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 7 deletions.
37 changes: 36 additions & 1 deletion packages/email-plugin/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
DefaultLogger,
EventBus,
Injector,
JobQueueService,
LanguageCode,
Logger,
LogLevel,
Expand Down Expand Up @@ -239,6 +240,37 @@ describe('EmailPlugin', () => {
expect(onSend.mock.calls[0][0].subject).toBe('Hello baz');
});

it('loads globalTemplateVars async', async () => {
const handler = new EmailEventListener('test')
.on(MockEvent)
.setFrom('"test from" <noreply@test.com>')
.setRecipient(() => 'test@test.com')
.setSubject('Job {{ name }}, {{ primaryColor }}');

await initPluginWithHandlers([handler], {
globalTemplateVars: async (_ctxLocal: RequestContext, injector: Injector) => {
const jobQueueService = injector.get(JobQueueService);
const jobQueue = await jobQueueService.createQueue({
name: 'hello-service',
// eslint-disable-next-line
process: async job => {
return 'hello';
},
});
const name = jobQueue.name;

return {
name,
primaryColor: 'blue',
};
},
});

eventBus.publish(new MockEvent(ctx, true));
await pause();
expect(onSend.mock.calls[0][0].subject).toBe(`Job hello-service, blue`);
});

it('interpolates from', async () => {
const handler = new EmailEventListener('test')
.on(MockEvent)
Expand Down Expand Up @@ -922,7 +954,10 @@ class FakeCustomSender implements EmailSender {
const pause = () => new Promise(resolve => setTimeout(resolve, 100));

class MockEvent extends VendureEvent {
constructor(public ctx: RequestContext, public shouldSend: boolean) {
constructor(
public ctx: RequestContext,
public shouldSend: boolean,
) {
super();
}
}
Expand Down
38 changes: 37 additions & 1 deletion packages/email-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,38 @@ import {
* </mj-table>
* ```
*
* ### Setting global variables using `globalTemplateVars`
*
* `globalTemplateVars` is an object that can be passed to the configuration of the Email Plugin with static object variables.
* You can also pass an async function that will be called with the `RequestContext` and the `Injector` so you can access services
* and e.g. load channel specific theme configurations.
*
* @example
* ```ts
* EmailPlugin.init({
* globalTemplateVars: {
* primaryColor: '#FF0000',
* fromAddress: 'no-reply@ourstore.com'
* }
* })
* ```
* or
* ```ts
* EmailPlugin.init({
* globalTemplateVars: async (ctx, injector) => {
* const myAsyncService = injector.get(MyAsyncService);
* const asyncValue = await myAsyncService.get(ctx);
* const channel = ctx.channel;
* const { primaryColor } = channel.customFields.theme;
* const theme = {
* primaryColor,
* asyncValue,
* };
* return theme;
* }
* })
* ```
*
* ### Handlebars helpers
*
* The following helper functions are available for use in email templates:
Expand Down Expand Up @@ -378,9 +410,13 @@ export class EmailPlugin implements OnApplicationBootstrap, OnApplicationShutdow
const { type } = handler;
try {
const injector = new Injector(this.moduleRef);
let globalTemplateVars = this.options.globalTemplateVars;
if (typeof globalTemplateVars === 'function') {
globalTemplateVars = await globalTemplateVars(event.ctx, injector);
}
const result = await handler.handle(
event as any,
EmailPlugin.options.globalTemplateVars,
globalTemplateVars as { [key: string]: any },
injector,
);
if (!result) {
Expand Down
48 changes: 43 additions & 5 deletions packages/email-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -31,6 +31,41 @@ export type EventWithContext = VendureEvent & { ctx: RequestContext };
*/
export type EventWithAsyncData<Event extends EventWithContext, R> = Event & { data: R };

/**
* @description
* Allows you to dynamically load the "globalTemplateVars" key async and access Vendure services
* to create the object. This is not a requirement. You can also specify a simple static object if your
* projects doesn't need to access async or dynamic values.
*
* @example
* ```ts
*
* EmailPlugin.init({
* globalTemplateVars: async (ctx, injector) => {
* const myAsyncService = injector.get(MyAsyncService);
* const asyncValue = await myAsyncService.get(ctx);
* const channel = ctx.channel;
* const { primaryColor } = channel.customFields.theme;
* const theme = {
* primaryColor,
* asyncValue,
* };
* return theme;
* }
* [...]
* })
*
* ```
*
* @docsCategory core plugins/EmailPlugin
* @docsPage EmailPluginOptions
* @docsWeight 0
*/
export type GlobalTemplateVarsFn = (
ctx: RequestContext,
injector: Injector,
) => Promise<{ [key: string]: any }>;

/**
* @description
* Configuration for the EmailPlugin.
Expand Down Expand Up @@ -75,9 +110,10 @@ export interface EmailPluginOptions {
* @description
* 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.
* email. Use the GlobalTemplateVarsFn if you need to retrieve variables from Vendure or
* plugin services.
*/
globalTemplateVars?: { [key: string]: any };
globalTemplateVars?: { [key: string]: any } | GlobalTemplateVarsFn;
/**
* @description
* An optional allowed EmailSender, used to allow custom implementations of the send functionality
Expand All @@ -97,9 +133,11 @@ export interface EmailPluginOptions {
}

/**
* EmailPLuginOptions type after initialization, where templateLoader is no longer optional
* EmailPLuginOptions type after initialization, where templateLoader and themeInjector are no longer optional
*/
export type InitializedEmailPluginOptions = EmailPluginOptions & { templateLoader: TemplateLoader };
export type InitializedEmailPluginOptions = EmailPluginOptions & {
templateLoader: TemplateLoader;
};

/**
* @description
Expand Down

0 comments on commit cab67b6

Please sign in to comment.