Browse Source

feat(server): Add templateVars option to EmailOptions

Allows better configurability of emails.
Michael Bromley 7 years ago
parent
commit
0efd5ff3ca

+ 3 - 0
server/cli/assets/vendure-config.hbs

@@ -55,6 +55,9 @@ const path = require('path');
         new DefaultEmailPlugin({
             templatePath: path.join(__dirname, 'vendure/email/templates'),
             devMode: true,
+            templateVars: {
+                shopUrl: 'http://www.example.com',
+            }
         }),
         new DefaultSearchPlugin(),
         new AdminUiPlugin({ port: 3002 }),

+ 4 - 1
server/dev-config.ts

@@ -25,7 +25,7 @@ export const devConfig: VendureConfig = {
     adminApiPath: ADMIN_API_PATH,
     shopApiPath: SHOP_API_PATH,
     dbConnectionOptions: {
-        synchronize: false,
+        synchronize: true,
         logging: false,
 
         type: 'mysql',
@@ -59,6 +59,9 @@ export const devConfig: VendureConfig = {
             raw: false,
             outputPath: path.join(__dirname, 'test-emails'),
         },
+        templateVars: {
+            shopUrl: 'http://localhost:4201/',
+        },
     },
     importExportOptions: {
         importAssetsDir: path.join(__dirname, 'import-assets'),

+ 1 - 0
server/src/config/default-config.ts

@@ -80,6 +80,7 @@ export const defaultConfig: ReadOnlyRequired<VendureConfig> = {
         transport: {
             type: 'none',
         },
+        templateVars: {},
     },
     importExportOptions: {
         importAssetsDir: __dirname,

+ 11 - 1
server/src/config/email/email-options.ts

@@ -4,18 +4,28 @@ import { EmailContext, GeneratedEmailContext } from '../../email/email-context';
 import { VendureEvent } from '../../event-bus/vendure-event';
 import { ConfigService } from '../config.service';
 
+/**
+ * @description
+ * This object defines the template location and context data used for interpolation
+ * of an email for a particular language of a particular channel.
+ *
+ * @docsCategory email
+ */
 export type TemplateConfig<C = any, R = any> = {
     /**
-     * A function which uses the EmailContext to provide a context object for the
+     * @description
+     * A function which uses the {@link EmailContext} to provide a context object for the
      * template engine. That is, the templates will have access to the object
      * returned by this function.
      */
     templateContext: (emailContext: C) => R;
     /**
+     * @description
      * The subject line for the email.
      */
     subject: string;
     /**
+     * @description
      * The path to the template file for the body of the email.
      */
     templatePath: string;

+ 8 - 0
server/src/config/vendure-config.ts

@@ -278,6 +278,14 @@ export interface EmailOptions<EmailType extends string> {
      * @default NoopTransportOptions
      */
     transport: EmailTransportOptions;
+    /**
+     * @description
+     * An object containing any extra variables for use in email templates. For example,
+     * the storefront URL could be defined here for use in password reset emails.
+     *
+     * @default {}
+     */
+    templateVars?: { [name: string]: any };
 }
 
 /**

+ 9 - 4
server/src/email/default-email-types.ts

@@ -3,10 +3,15 @@ import { AccountRegistrationEvent } from '../event-bus/events/account-registrati
 import { OrderStateTransitionEvent } from '../event-bus/events/order-state-transition-event';
 import { PasswordResetEvent } from '../event-bus/events/password-reset-event';
 
+/**
+ * @description
+ * The possible types of email which are configured by default. These define the keys of the
+ * {@link EmailTypes} object.
+ *
+ * @docsCategory email
+ */
 export type DefaultEmailType = 'order-confirmation' | 'email-verification' | 'password-reset';
 
-const SHOPFRONT_URL = 'http://localhost:4201/';
-
 export const defaultEmailTypes: EmailTypes<DefaultEmailType> = {
     'order-confirmation': configEmailType({
         triggerEvent: OrderStateTransitionEvent,
@@ -44,7 +49,7 @@ export const defaultEmailTypes: EmailTypes<DefaultEmailType> = {
                 defaultLanguage: {
                     templateContext: emailContext => ({
                         user: emailContext.event.user,
-                        verifyUrl: SHOPFRONT_URL + 'verify',
+                        verifyUrl: emailContext.templateVars.shopUrl + 'verify',
                     }),
                     subject: `Please verify your email address`,
                     templatePath: 'email-verification/email-verification.hbs',
@@ -66,7 +71,7 @@ export const defaultEmailTypes: EmailTypes<DefaultEmailType> = {
                 defaultLanguage: {
                     templateContext: emailContext => ({
                         user: emailContext.event.user,
-                        passwordResetUrl: SHOPFRONT_URL + 'reset-password',
+                        passwordResetUrl: emailContext.templateVars.shopUrl + 'reset-password',
                     }),
                     subject: `Forgotten password reset`,
                     templatePath: 'password-reset/password-reset.hbs',

+ 30 - 3
server/src/email/email-context.ts

@@ -4,21 +4,46 @@ import { VendureEvent } from '../event-bus/vendure-event';
 
 /**
  * @description
- * The EmailContext contains all the neccesary data required to generate an email
- * (subject, body).
+ * The EmailContext contains all the neccesary data required to generate an email.
+ * It is used in the `templateContext` method of the {@link TemplateConfig} object
+ * to define which data get passed to the email template engine for interpolation.
  *
  * @docsCategory email
  */
 export class EmailContext<T extends string = any, E extends VendureEvent = any> {
+    /**
+     * @description
+     * A string defining the type of email, e.g. "order-confirmation". See {@link DefaultEmailType}
+     * for the default types available.
+     */
     public readonly type: T;
+    /**
+     * @description
+     * The email address of the email recipient.
+     */
     public readonly recipient: string;
     /**
      * @description
      * The {@link VendureEvent} which triggered this email
      */
     public readonly event: E;
+    /**
+     * @description
+     * The code of the active language when the even was fired.
+     */
     public readonly languageCode: LanguageCode;
+    /**
+     * @description
+     * The code of the Channel from which the event triggering the email was
+     * fired.
+     */
     public readonly channelCode: string;
+    /**
+     * @description
+     * The variables defined in the {@link EmailOptions} which can be used in
+     * the email templates.
+     */
+    public readonly templateVars: { [name: string]: any };
 
     constructor(options: {
         type: T;
@@ -26,13 +51,15 @@ export class EmailContext<T extends string = any, E extends VendureEvent = any>
         channelCode: string;
         recipient: string;
         event: E;
+        templateVars: { [name: string]: any };
     }) {
-        const { type, recipient, event, languageCode, channelCode } = options;
+        const { type, recipient, event, languageCode, channelCode, templateVars } = options;
         this.type = type;
         this.languageCode = languageCode;
         this.channelCode = channelCode;
         this.recipient = recipient;
         this.event = event;
+        this.templateVars = templateVars;
     }
 }
 

+ 2 - 1
server/src/email/email.module.ts

@@ -48,13 +48,14 @@ export class EmailModule implements OnModuleInit {
     }
 
     private async handleEvent(type: string, config: EmailTypeConfig<any>, event: VendureEvent) {
-        const { generator, transport } = this.configService.emailOptions;
+        const { generator, transport, templateVars } = this.configService.emailOptions;
         const contextConfig = config.createContext(event);
         if (contextConfig) {
             const emailContext = new EmailContext({
                 ...contextConfig,
                 type,
                 event,
+                templateVars,
             });
             const { subject, body, templateContext } = await this.templateLoader.loadTemplate(
                 type,

+ 2 - 0
server/src/email/preview/email-contexts.ts

@@ -105,6 +105,7 @@ export function getOrderReceiptContext():
             ...contextConfig,
             type: 'order-confirmation',
             event,
+            templateVars: {},
         });
     }
 }
@@ -126,6 +127,7 @@ export function getEmailVerificationContext():
             ...contextConfig,
             type: 'email-verification',
             event,
+            templateVars: {},
         });
     }
 }

+ 9 - 0
server/src/plugin/default-email-plugin/default-email-plugin.ts

@@ -22,11 +22,17 @@ export interface DefaultEmailPluginOptions {
      * Configures how the emails are sent.
      */
     transport: EmailTransportOptions;
+    /**
+     * @description
+     * Variables for use in email templates
+     */
+    templateVars: { [name: string]: any };
 }
 
 export interface DefaultEmailPluginDevModeOptions {
     templatePath: string;
     devMode: true;
+    templateVars: { [name: string]: any };
 }
 
 /**
@@ -35,9 +41,11 @@ export interface DefaultEmailPluginDevModeOptions {
 export class DefaultEmailPlugin implements VendurePlugin {
     private readonly templatePath: string;
     private readonly transport: EmailTransportOptions;
+    private readonly templateVars: { [name: string]: any };
 
     constructor(options: DefaultEmailPluginOptions | DefaultEmailPluginDevModeOptions) {
         this.templatePath = options.templatePath;
+        this.templateVars = options.templateVars;
         if (isDevModeOptions(options)) {
             this.transport = {
                 type: 'file',
@@ -60,6 +68,7 @@ export class DefaultEmailPlugin implements VendurePlugin {
             emailTypes: defaultEmailTypes,
             generator: new HandlebarsMjmlGenerator(),
             transport: this.transport,
+            templateVars: this.templateVars,
         };
         return config;
     }