@deenruv/email-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +85 -0
  3. package/dev-mailbox.html +218 -0
  4. package/lib/index.d.ts +13 -0
  5. package/lib/index.js +30 -0
  6. package/lib/index.js.map +1 -0
  7. package/lib/src/attachment-utils.d.ts +3 -0
  8. package/lib/src/attachment-utils.js +66 -0
  9. package/lib/src/attachment-utils.js.map +1 -0
  10. package/lib/src/common.d.ts +4 -0
  11. package/lib/src/common.js +17 -0
  12. package/lib/src/common.js.map +1 -0
  13. package/lib/src/constants.d.ts +2 -0
  14. package/lib/src/constants.js +6 -0
  15. package/lib/src/constants.js.map +1 -0
  16. package/lib/src/dev-mailbox.d.ts +14 -0
  17. package/lib/src/dev-mailbox.js +116 -0
  18. package/lib/src/dev-mailbox.js.map +1 -0
  19. package/lib/src/email-processor.d.ts +21 -0
  20. package/lib/src/email-processor.js +109 -0
  21. package/lib/src/email-processor.js.map +1 -0
  22. package/lib/src/email-send-event.d.ts +18 -0
  23. package/lib/src/email-send-event.js +24 -0
  24. package/lib/src/email-send-event.js.map +1 -0
  25. package/lib/src/event-listener.d.ts +19 -0
  26. package/lib/src/event-listener.js +25 -0
  27. package/lib/src/event-listener.js.map +1 -0
  28. package/lib/src/generator/email-generator.d.ts +25 -0
  29. package/lib/src/generator/email-generator.js +3 -0
  30. package/lib/src/generator/email-generator.js.map +1 -0
  31. package/lib/src/generator/handlebars-mjml-generator.d.ts +19 -0
  32. package/lib/src/generator/handlebars-mjml-generator.js +78 -0
  33. package/lib/src/generator/handlebars-mjml-generator.js.map +1 -0
  34. package/lib/src/generator/noop-email-generator.d.ts +11 -0
  35. package/lib/src/generator/noop-email-generator.js +13 -0
  36. package/lib/src/generator/noop-email-generator.js.map +1 -0
  37. package/lib/src/generator/react-email-generator.d.ts +7 -0
  38. package/lib/src/generator/react-email-generator.js +40 -0
  39. package/lib/src/generator/react-email-generator.js.map +1 -0
  40. package/lib/src/handler/default-email-handlers.d.ts +32 -0
  41. package/lib/src/handler/default-email-handlers.js +111 -0
  42. package/lib/src/handler/default-email-handlers.js.map +1 -0
  43. package/lib/src/handler/event-handler.d.ts +276 -0
  44. package/lib/src/handler/event-handler.js +396 -0
  45. package/lib/src/handler/event-handler.js.map +1 -0
  46. package/lib/src/handler/mock-events.d.ts +5 -0
  47. package/lib/src/handler/mock-events.js +119 -0
  48. package/lib/src/handler/mock-events.js.map +1 -0
  49. package/lib/src/plugin.d.ts +301 -0
  50. package/lib/src/plugin.js +428 -0
  51. package/lib/src/plugin.js.map +1 -0
  52. package/lib/src/sender/email-sender.d.ts +45 -0
  53. package/lib/src/sender/email-sender.js +3 -0
  54. package/lib/src/sender/email-sender.js.map +1 -0
  55. package/lib/src/sender/nodemailer-email-sender.d.ts +37 -0
  56. package/lib/src/sender/nodemailer-email-sender.js +151 -0
  57. package/lib/src/sender/nodemailer-email-sender.js.map +1 -0
  58. package/lib/src/template-loader/file-based-template-loader.d.ts +17 -0
  59. package/lib/src/template-loader/file-based-template-loader.js +37 -0
  60. package/lib/src/template-loader/file-based-template-loader.js.map +1 -0
  61. package/lib/src/template-loader/react-email-template-loader.d.ts +6 -0
  62. package/lib/src/template-loader/react-email-template-loader.js +14 -0
  63. package/lib/src/template-loader/react-email-template-loader.js.map +1 -0
  64. package/lib/src/template-loader/template-loader.d.ts +42 -0
  65. package/lib/src/template-loader/template-loader.js +3 -0
  66. package/lib/src/template-loader/template-loader.js.map +1 -0
  67. package/lib/src/types.d.ts +453 -0
  68. package/lib/src/types.js +3 -0
  69. package/lib/src/types.js.map +1 -0
  70. package/package.json +51 -0
  71. package/templates/email-address-change/body.hbs +20 -0
  72. package/templates/email-verification/body.hbs +20 -0
  73. package/templates/order-confirmation/body.hbs +133 -0
  74. package/templates/partials/footer.hbs +10 -0
  75. package/templates/partials/header.hbs +18 -0
  76. package/templates/password-reset/body.hbs +24 -0
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var EmailPlugin_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.EmailPlugin = exports.ReactComponentEmailGenerator = exports.ReactComponentLoader = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@nestjs/core");
19
+ const core_2 = require("@deenruv/core");
20
+ const common_2 = require("./common");
21
+ const constants_1 = require("./constants");
22
+ const dev_mailbox_1 = require("./dev-mailbox");
23
+ const email_processor_1 = require("./email-processor");
24
+ const file_based_template_loader_1 = require("./template-loader/file-based-template-loader");
25
+ var react_email_template_loader_1 = require("./template-loader/react-email-template-loader");
26
+ Object.defineProperty(exports, "ReactComponentLoader", { enumerable: true, get: function () { return react_email_template_loader_1.ReactComponentLoader; } });
27
+ var react_email_generator_1 = require("./generator/react-email-generator");
28
+ Object.defineProperty(exports, "ReactComponentEmailGenerator", { enumerable: true, get: function () { return react_email_generator_1.ReactComponentEmailGenerator; } });
29
+ /**
30
+ * @description
31
+ * The EmailPlugin creates and sends transactional emails based on Deenruv events. By default, it uses an [MJML](https://mjml.io/)-based
32
+ * email generator to generate the email body and [Nodemailer](https://nodemailer.com/about/) to send the emails.
33
+ *
34
+ * ## High-level description
35
+ * Deenruv has an internal events system (see {@link EventBus}) that allows plugins to subscribe to events. The EmailPlugin is configured with {@link EmailEventHandler}s
36
+ * that listen for a specific event and when it is published, the handler defines which template to use to generate the resulting email.
37
+ *
38
+ * The plugin comes with a set of default handler for the following events:
39
+ * - Order confirmation
40
+ * - New customer email address verification
41
+ * - Password reset request
42
+ * - Email address change request
43
+ *
44
+ * You can also create your own handler and register them with the plugin - see the {@link EmailEventHandler} docs for more details.
45
+ *
46
+ * ## Installation
47
+ *
48
+ * `yarn add \@deenruv/email-plugin`
49
+ *
50
+ * or
51
+ *
52
+ * `npm install \@deenruv/email-plugin`
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { defaultEmailHandlers, EmailPlugin } from '\@deenruv/email-plugin';
57
+ *
58
+ * const config: DeenruvConfig = {
59
+ * // Add an instance of the plugin to the plugins array
60
+ * plugins: [
61
+ * EmailPlugin.init({
62
+ * handler: defaultEmailHandlers,
63
+ * templatePath: path.join(__dirname, 'static/email/templates'),
64
+ * transport: {
65
+ * type: 'smtp',
66
+ * host: 'smtp.example.com',
67
+ * port: 587,
68
+ * auth: {
69
+ * user: 'username',
70
+ * pass: 'password',
71
+ * }
72
+ * },
73
+ * }),
74
+ * ],
75
+ * };
76
+ * ```
77
+ *
78
+ * ## Email templates
79
+ *
80
+ * In the example above, the plugin has been configured to look in `<app-root>/static/email/templates`
81
+ * for the email template files. If you used `\@deenruv/create` to create your application, the templates will have
82
+ * been copied to that location during setup.
83
+ *
84
+ * If you are installing the EmailPlugin separately, then you'll need to copy the templates manually from
85
+ * `node_modules/\@deenruv/email-plugin/templates` to a location of your choice, and then point the `templatePath` config
86
+ * property at that directory.
87
+ *
88
+ * * ### Dynamic Email Templates
89
+ * Instead of passing a static value to `templatePath`, use `templateLoader` to define a template path.
90
+ * ```ts
91
+ * EmailPlugin.init({
92
+ * ...,
93
+ * templateLoader: new FileBasedTemplateLoader(my/order-confirmation/templates)
94
+ * })
95
+ * ```
96
+ * ## Customizing templates
97
+ *
98
+ * Emails are generated from templates which use [MJML](https://mjml.io/) syntax. MJML is an open-source HTML-like markup
99
+ * language which makes the task of creating responsive email markup simple. By default, the templates are installed to
100
+ * `<project root>/deenruv/email/templates` and can be freely edited.
101
+ *
102
+ * Dynamic data such as the recipient's name or order items are specified using [Handlebars syntax](https://handlebarsjs.com/):
103
+ *
104
+ * ```html
105
+ * <p>Dear {{ order.customer.firstName }} {{ order.customer.lastName }},</p>
106
+ *
107
+ * <p>Thank you for your order!</p>
108
+ *
109
+ * <mj-table cellpadding="6px">
110
+ * {{#each order.lines }}
111
+ * <tr class="order-row">
112
+ * <td>{{ quantity }} x {{ productVariant.name }}</td>
113
+ * <td>{{ productVariant.quantity }}</td>
114
+ * <td>{{ formatMoney totalPrice }}</td>
115
+ * </tr>
116
+ * {{/each}}
117
+ * </mj-table>
118
+ * ```
119
+ *
120
+ * ### Setting global variables using `globalTemplateVars`
121
+ *
122
+ * `globalTemplateVars` is an object that can be passed to the configuration of the Email Plugin with static object variables.
123
+ * You can also pass an async function that will be called with the `RequestContext` and the `Injector` so you can access services
124
+ * and e.g. load channel specific theme configurations.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * EmailPlugin.init({
129
+ * globalTemplateVars: {
130
+ * primaryColor: '#FF0000',
131
+ * fromAddress: 'no-reply@ourstore.com'
132
+ * }
133
+ * })
134
+ * ```
135
+ * or
136
+ * ```ts
137
+ * EmailPlugin.init({
138
+ * globalTemplateVars: async (ctx, injector) => {
139
+ * const myAsyncService = injector.get(MyAsyncService);
140
+ * const asyncValue = await myAsyncService.get(ctx);
141
+ * const channel = ctx.channel;
142
+ * const { primaryColor } = channel.customFields.theme;
143
+ * const theme = {
144
+ * primaryColor,
145
+ * asyncValue,
146
+ * };
147
+ * return theme;
148
+ * }
149
+ * })
150
+ * ```
151
+ *
152
+ * ### Handlebars helpers
153
+ *
154
+ * The following helper functions are available for use in email templates:
155
+ *
156
+ * * `formatMoney`: Formats an amount of money (which are always stored as integers in Deenruv) as a decimal, e.g. `123` => `1.23`
157
+ * * `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
158
+ *
159
+ * ## Extending the default email handler
160
+ *
161
+ * The `defaultEmailHandlers` array defines the default handler such as for handling new account registration, order confirmation, password reset
162
+ * etc. These defaults can be extended by adding custom templates for languages other than the default, or even completely new types of emails
163
+ * which respond to any of the available [DeenruvEvents](/reference/typescript-api/events/).
164
+ *
165
+ * A good way to learn how to create your own email handler is to take a look at the
166
+ * [source code of the default handler](https://github.com/deenruv-ecommerce/deenruv/blob/master/packages/email-plugin/src/handler/default-email-handlers.ts).
167
+ * New handler are defined in exactly the same way.
168
+ *
169
+ * It is also possible to modify the default handler:
170
+ *
171
+ * ```ts
172
+ * // Rather than importing `defaultEmailHandlers`, you can
173
+ * // import the handler individually
174
+ * import {
175
+ * orderConfirmationHandler,
176
+ * emailVerificationHandler,
177
+ * passwordResetHandler,
178
+ * emailAddressChangeHandler,
179
+ * } from '\@deenruv/email-plugin';
180
+ * import { CustomerService } from '\@deenruv/core';
181
+ *
182
+ * // This allows you to then customize each handler to your needs.
183
+ * // For example, let's set a new subject line to the order confirmation:
184
+ * const myOrderConfirmationHandler = orderConfirmationHandler
185
+ * .setSubject(`We received your order!`);
186
+ *
187
+ * // Another example: loading additional data and setting new
188
+ * // template variables.
189
+ * const myPasswordResetHandler = passwordResetHandler
190
+ * .loadData(async ({ event, injector }) => {
191
+ * const customerService = injector.get(CustomerService);
192
+ * const customer = await customerService.findOneByUserId(event.ctx, event.user.id);
193
+ * return { customer };
194
+ * })
195
+ * .setTemplateVars(event => ({
196
+ * passwordResetToken: event.user.getNativeAuthenticationMethod().passwordResetToken,
197
+ * customer: event.data.customer,
198
+ * }));
199
+ *
200
+ * // Then you pass the handler to the EmailPlugin init method
201
+ * // individually
202
+ * EmailPlugin.init({
203
+ * handler: [
204
+ * myOrderConfirmationHandler,
205
+ * myPasswordResetHandler,
206
+ * emailVerificationHandler,
207
+ * emailAddressChangeHandler,
208
+ * ],
209
+ * // ...
210
+ * }),
211
+ * ```
212
+ *
213
+ * For all available methods of extending a handler, see the {@link EmailEventHandler} documentation.
214
+ *
215
+ * ## Dynamic SMTP settings
216
+ *
217
+ * Instead of defining static transport settings, you can also provide a function that dynamically resolves
218
+ * channel aware transport settings.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * import { defaultEmailHandlers, EmailPlugin } from '\@deenruv/email-plugin';
223
+ * import { MyTransportService } from './transport.services.ts';
224
+ * const config: DeenruvConfig = {
225
+ * plugins: [
226
+ * EmailPlugin.init({
227
+ * handler: defaultEmailHandlers,
228
+ * templatePath: path.join(__dirname, 'static/email/templates'),
229
+ * transport: (injector, ctx) => {
230
+ * if (ctx) {
231
+ * return injector.get(MyTransportService).getSettings(ctx);
232
+ * } else {
233
+ * return {
234
+ * type: 'smtp',
235
+ * host: 'smtp.example.com',
236
+ * // ... etc.
237
+ * }
238
+ * }
239
+ * }
240
+ * }),
241
+ * ],
242
+ * };
243
+ * ```
244
+ *
245
+ * ## Dev mode
246
+ *
247
+ * For development, the `transport` option can be replaced by `devMode: true`. Doing so configures Deenruv to use the
248
+ * file transport (See {@link FileTransportOptions}) and outputs emails as rendered HTML files in the directory specified by the
249
+ * `outputPath` property.
250
+ *
251
+ * ```ts
252
+ * EmailPlugin.init({
253
+ * devMode: true,
254
+ * route: 'mailbox',
255
+ * handler: defaultEmailHandlers,
256
+ * templatePath: path.join(__dirname, 'deenruv/email/templates'),
257
+ * outputPath: path.join(__dirname, 'test-emails'),
258
+ * })
259
+ * ```
260
+ *
261
+ * ### Dev mailbox
262
+ *
263
+ * In dev mode, a webmail-like interface available at the `/mailbox` path, e.g.
264
+ * http://localhost:3000/mailbox. This is a simple way to view the output of all emails generated by the EmailPlugin while in dev mode.
265
+ *
266
+ * ## Troubleshooting SMTP Connections
267
+ *
268
+ * If you are having trouble sending email over and SMTP connection, set the `logging` and `debug` options to `true`. This will
269
+ * send detailed information from the SMTP transporter to the configured logger (defaults to console). For maximum detail combine
270
+ * this with a detail log level in the configured DeenruvLogger:
271
+ *
272
+ * ```ts
273
+ * const config: DeenruvConfig = {
274
+ * logger: new DefaultLogger({ level: LogLevel.Debug })
275
+ * // ...
276
+ * plugins: [
277
+ * EmailPlugin.init({
278
+ * // ...
279
+ * transport: {
280
+ * type: 'smtp',
281
+ * host: 'smtp.example.com',
282
+ * port: 587,
283
+ * auth: {
284
+ * user: 'username',
285
+ * pass: 'password',
286
+ * },
287
+ * logging: true,
288
+ * debug: true,
289
+ * },
290
+ * }),
291
+ * ],
292
+ * };
293
+ * ```
294
+ *
295
+ * @docsCategory core plugins/EmailPlugin
296
+ */
297
+ let EmailPlugin = EmailPlugin_1 = class EmailPlugin {
298
+ /** @internal */
299
+ constructor(eventBus, moduleRef, emailProcessor, jobQueueService, processContext, options) {
300
+ this.eventBus = eventBus;
301
+ this.moduleRef = moduleRef;
302
+ this.emailProcessor = emailProcessor;
303
+ this.jobQueueService = jobQueueService;
304
+ this.processContext = processContext;
305
+ this.options = options;
306
+ }
307
+ /**
308
+ * Set the plugin options.
309
+ */
310
+ static init(options) {
311
+ if (options.templateLoader) {
312
+ core_2.Logger.info(`Using custom template loader '${options.templateLoader.constructor.name}'`);
313
+ }
314
+ else if (!options.templateLoader && options.templatePath) {
315
+ // TODO: this else-if can be removed when deprecated templatePath is removed,
316
+ // because we will either have a custom template loader, or the default loader with a default path
317
+ options.templateLoader = new file_based_template_loader_1.FileBasedTemplateLoader(options.templatePath);
318
+ }
319
+ else {
320
+ throw new Error("You must either supply a templatePath or provide a custom templateLoader");
321
+ }
322
+ this.options = options;
323
+ return EmailPlugin_1;
324
+ }
325
+ /** @internal */
326
+ async onApplicationBootstrap() {
327
+ await this.initInjectableStrategies();
328
+ await this.setupEventSubscribers();
329
+ const transport = await (0, common_2.resolveTransportSettings)(this.options, new core_2.Injector(this.moduleRef));
330
+ if (!(0, common_2.isDevModeOptions)(this.options) && transport.type === "testing") {
331
+ // When running tests, we don't want to go through the JobQueue system,
332
+ // so we just call the email sending logic directly.
333
+ this.testingProcessor = new email_processor_1.EmailProcessor(this.options, this.moduleRef, this.eventBus);
334
+ await this.testingProcessor.init();
335
+ }
336
+ else {
337
+ await this.emailProcessor.init();
338
+ this.jobQueue = await this.jobQueueService.createQueue({
339
+ name: "send-email",
340
+ process: (job) => {
341
+ return this.emailProcessor.process(job.data);
342
+ },
343
+ });
344
+ }
345
+ }
346
+ async onApplicationShutdown() {
347
+ await this.destroyInjectableStrategies();
348
+ }
349
+ configure(consumer) {
350
+ if ((0, common_2.isDevModeOptions)(this.options) && this.processContext.isServer) {
351
+ core_2.Logger.info("Creating dev mailbox middleware", constants_1.loggerCtx);
352
+ this.devMailbox = new dev_mailbox_1.DevMailbox();
353
+ consumer
354
+ .apply(this.devMailbox.serve(this.options))
355
+ .forRoutes(this.options.route);
356
+ this.devMailbox.handleMockEvent((handler, event) => this.handleEvent(handler, event));
357
+ (0, core_2.registerPluginStartupMessage)("Dev mailbox", this.options.route);
358
+ }
359
+ }
360
+ async initInjectableStrategies() {
361
+ var _a, _b;
362
+ const injector = new core_2.Injector(this.moduleRef);
363
+ if (typeof ((_a = this.options.emailGenerator) === null || _a === void 0 ? void 0 : _a.init) === "function") {
364
+ await this.options.emailGenerator.init(injector);
365
+ }
366
+ if (typeof ((_b = this.options.emailSender) === null || _b === void 0 ? void 0 : _b.init) === "function") {
367
+ await this.options.emailSender.init(injector);
368
+ }
369
+ }
370
+ async destroyInjectableStrategies() {
371
+ var _a, _b;
372
+ if (typeof ((_a = this.options.emailGenerator) === null || _a === void 0 ? void 0 : _a.destroy) === "function") {
373
+ await this.options.emailGenerator.destroy();
374
+ }
375
+ if (typeof ((_b = this.options.emailSender) === null || _b === void 0 ? void 0 : _b.destroy) === "function") {
376
+ await this.options.emailSender.destroy();
377
+ }
378
+ }
379
+ async setupEventSubscribers() {
380
+ for (const handler of EmailPlugin_1.options.handlers) {
381
+ this.eventBus.ofType(handler.event).subscribe((event) => {
382
+ return this.handleEvent(handler, event);
383
+ });
384
+ }
385
+ }
386
+ async handleEvent(handler, event) {
387
+ core_2.Logger.debug(`Handling event "${handler.type}"`, constants_1.loggerCtx);
388
+ const { type } = handler;
389
+ try {
390
+ const injector = new core_2.Injector(this.moduleRef);
391
+ let globalTemplateVars = this.options.globalTemplateVars;
392
+ if (typeof globalTemplateVars === "function") {
393
+ globalTemplateVars = await globalTemplateVars(event.ctx, injector);
394
+ }
395
+ const result = await handler.handle(event, globalTemplateVars, injector);
396
+ if (!result) {
397
+ return;
398
+ }
399
+ if (this.jobQueue) {
400
+ await this.jobQueue.add(result, { retries: 5 });
401
+ }
402
+ else if (this.testingProcessor) {
403
+ await this.testingProcessor.process(result);
404
+ }
405
+ }
406
+ catch (e) {
407
+ core_2.Logger.error(e.message, constants_1.loggerCtx, e.stack);
408
+ }
409
+ }
410
+ };
411
+ exports.EmailPlugin = EmailPlugin;
412
+ exports.EmailPlugin = EmailPlugin = EmailPlugin_1 = __decorate([
413
+ (0, core_2.DeenruvPlugin)({
414
+ imports: [core_2.PluginCommonModule],
415
+ providers: [
416
+ { provide: constants_1.EMAIL_PLUGIN_OPTIONS, useFactory: () => EmailPlugin.options },
417
+ email_processor_1.EmailProcessor,
418
+ ],
419
+ compatibility: "^0.0.0",
420
+ }),
421
+ __param(5, (0, common_1.Inject)(constants_1.EMAIL_PLUGIN_OPTIONS)),
422
+ __metadata("design:paramtypes", [core_2.EventBus,
423
+ core_1.ModuleRef,
424
+ email_processor_1.EmailProcessor,
425
+ core_2.JobQueueService,
426
+ core_2.ProcessContext, Object])
427
+ ], EmailPlugin);
428
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { InjectableStrategy } from "@deenruv/core";
2
+ import { EmailDetails, EmailTransportOptions } from "../types";
3
+ /**
4
+ * @description
5
+ * An EmailSender is responsible for sending the email, e.g. via an SMTP connection
6
+ * or using some other mail-sending API. By default, the EmailPlugin uses the
7
+ * {@link NodemailerEmailSender}, but it is also possible to supply a custom implementation:
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const sgMail = require('\@sendgrid/mail');
12
+ *
13
+ * sgMail.setApiKey(process.env.SENDGRID_API_KEY);
14
+ *
15
+ * class SendgridEmailSender implements EmailSender {
16
+ * async send(email: EmailDetails) {
17
+ * await sgMail.send({
18
+ * to: email.recipient,
19
+ * from: email.from,
20
+ * subject: email.subject,
21
+ * html: email.body,
22
+ * });
23
+ * }
24
+ * }
25
+ *
26
+ * const config: DeenruvConfig = {
27
+ * logger: new DefaultLogger({ level: LogLevel.Debug })
28
+ * // ...
29
+ * plugins: [
30
+ * EmailPlugin.init({
31
+ * // ... template, handler config omitted
32
+ * transport: { type: 'none' },
33
+ * emailSender: new SendgridEmailSender(),
34
+ * }),
35
+ * ],
36
+ * };
37
+ * ```
38
+ *
39
+ * @docsCategory core plugins/EmailPlugin
40
+ * @docsPage EmailSender
41
+ * @docsWeight 0
42
+ */
43
+ export interface EmailSender extends InjectableStrategy {
44
+ send: (email: EmailDetails, options: EmailTransportOptions) => void | Promise<void>;
45
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=email-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-sender.js","sourceRoot":"","sources":["../../../src/sender/email-sender.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ import { Stream } from "stream";
3
+ import { EmailDetails, EmailTransportOptions } from "../types";
4
+ import { EmailSender } from "./email-sender";
5
+ export type StreamTransportInfo = {
6
+ envelope: {
7
+ from: string;
8
+ to: string[];
9
+ };
10
+ messageId: string;
11
+ message: Stream;
12
+ };
13
+ /**
14
+ * @description
15
+ * Uses the configured transport to send the generated email.
16
+ *
17
+ * @docsCategory core plugins/EmailPlugin
18
+ * @docsPage EmailSender
19
+ */
20
+ export declare class NodemailerEmailSender implements EmailSender {
21
+ private _smtpTransport;
22
+ private _sendMailTransport;
23
+ private _sesTransport;
24
+ send(email: EmailDetails, options: EmailTransportOptions): Promise<undefined>;
25
+ private getSmtpTransport;
26
+ private getSesTransport;
27
+ private getSendMailTransport;
28
+ private sendMail;
29
+ private sendFileJson;
30
+ private sendFileRaw;
31
+ private writeStreamToFile;
32
+ /**
33
+ * Adapts the DeenruvLogger to work with the bunyan-compatible logger format
34
+ * used by Nodemailer.
35
+ */
36
+ private createLogger;
37
+ }
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NodemailerEmailSender = void 0;
7
+ const normalize_string_1 = require("@deenruv/common/lib/normalize-string");
8
+ const shared_utils_1 = require("@deenruv/common/lib/shared-utils");
9
+ const core_1 = require("@deenruv/core");
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const nodemailer_1 = require("nodemailer");
12
+ const path_1 = __importDefault(require("path"));
13
+ const util_1 = require("util");
14
+ const constants_1 = require("../constants");
15
+ /**
16
+ * @description
17
+ * Uses the configured transport to send the generated email.
18
+ *
19
+ * @docsCategory core plugins/EmailPlugin
20
+ * @docsPage EmailSender
21
+ */
22
+ class NodemailerEmailSender {
23
+ async send(email, options) {
24
+ switch (options.type) {
25
+ case "none":
26
+ return;
27
+ break;
28
+ case "file":
29
+ const fileName = (0, normalize_string_1.normalizeString)(`${new Date().toISOString()} ${email.recipient} ${email.subject}`, "_");
30
+ const filePath = path_1.default.join(options.outputPath, fileName);
31
+ if (options.raw) {
32
+ await this.sendFileRaw(email, filePath);
33
+ }
34
+ else {
35
+ await this.sendFileJson(email, filePath);
36
+ }
37
+ break;
38
+ case "sendmail":
39
+ await this.sendMail(email, this.getSendMailTransport(options));
40
+ break;
41
+ case "ses":
42
+ await this.sendMail(email, this.getSesTransport(options));
43
+ break;
44
+ case "smtp":
45
+ await this.sendMail(email, this.getSmtpTransport(options));
46
+ break;
47
+ case "testing":
48
+ options.onSend(email);
49
+ break;
50
+ default:
51
+ return (0, shared_utils_1.assertNever)(options);
52
+ }
53
+ }
54
+ getSmtpTransport(options) {
55
+ if (!this._smtpTransport) {
56
+ options.logger = options.logging ? this.createLogger() : false;
57
+ this._smtpTransport = (0, nodemailer_1.createTransport)(options);
58
+ }
59
+ return this._smtpTransport;
60
+ }
61
+ getSesTransport(options) {
62
+ if (!this._sesTransport) {
63
+ this._sesTransport = (0, nodemailer_1.createTransport)(options);
64
+ }
65
+ return this._sesTransport;
66
+ }
67
+ getSendMailTransport(options) {
68
+ if (!this._sendMailTransport) {
69
+ this._sendMailTransport = (0, nodemailer_1.createTransport)(Object.assign({ sendmail: true }, options));
70
+ }
71
+ return this._sendMailTransport;
72
+ }
73
+ async sendMail(email, transporter) {
74
+ return transporter.sendMail({
75
+ from: email.from,
76
+ to: email.recipient,
77
+ subject: email.subject,
78
+ html: email.body,
79
+ attachments: email.attachments,
80
+ cc: email.cc,
81
+ bcc: email.bcc,
82
+ replyTo: email.replyTo,
83
+ });
84
+ }
85
+ async sendFileJson(email, pathWithoutExt) {
86
+ const output = {
87
+ date: new Date().toLocaleString(),
88
+ from: email.from,
89
+ recipient: email.recipient,
90
+ subject: email.subject,
91
+ body: email.body,
92
+ cc: email.cc,
93
+ bcc: email.bcc,
94
+ replyTo: email.replyTo,
95
+ };
96
+ await fs_extra_1.default.writeFile(pathWithoutExt + ".json", JSON.stringify(output, null, 2));
97
+ }
98
+ async sendFileRaw(email, pathWithoutExt) {
99
+ const transporter = (0, nodemailer_1.createTransport)({
100
+ streamTransport: true,
101
+ buffer: true,
102
+ });
103
+ const result = await this.sendMail(email, transporter);
104
+ await this.writeStreamToFile(pathWithoutExt + ".txt", result);
105
+ }
106
+ async writeStreamToFile(filePath, info) {
107
+ const writeStream = fs_extra_1.default.createWriteStream(filePath);
108
+ return new Promise((resolve, reject) => {
109
+ writeStream.on("open", () => {
110
+ info.message.pipe(writeStream);
111
+ writeStream.on("close", () => resolve(filePath));
112
+ writeStream.on("error", reject);
113
+ });
114
+ });
115
+ }
116
+ /**
117
+ * Adapts the DeenruvLogger to work with the bunyan-compatible logger format
118
+ * used by Nodemailer.
119
+ */
120
+ createLogger() {
121
+ function formatError(args) {
122
+ const [ctx, message, ...params] = args;
123
+ return (0, util_1.format)(message, ...params);
124
+ }
125
+ return {
126
+ level(level) {
127
+ /* noop */
128
+ },
129
+ trace(...params) {
130
+ core_1.Logger.debug(formatError(params), constants_1.loggerCtx);
131
+ },
132
+ debug(...params) {
133
+ core_1.Logger.verbose(formatError(params), constants_1.loggerCtx);
134
+ },
135
+ info(...params) {
136
+ core_1.Logger.info(formatError(params), constants_1.loggerCtx);
137
+ },
138
+ warn(...params) {
139
+ core_1.Logger.warn(formatError(params), constants_1.loggerCtx);
140
+ },
141
+ error(...params) {
142
+ core_1.Logger.error(formatError(params), constants_1.loggerCtx);
143
+ },
144
+ fatal(...params) {
145
+ core_1.Logger.error(formatError(params), constants_1.loggerCtx);
146
+ },
147
+ };
148
+ }
149
+ }
150
+ exports.NodemailerEmailSender = NodemailerEmailSender;
151
+ //# sourceMappingURL=nodemailer-email-sender.js.map