email-processor.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { Inject, Injectable } from '@nestjs/common';
  2. import { InternalServerError, Logger } from '@vendure/core';
  3. import fs from 'fs-extra';
  4. import { deserializeAttachments } from './attachment-utils';
  5. import { isDevModeOptions } from './common';
  6. import { EMAIL_PLUGIN_OPTIONS, loggerCtx } from './constants';
  7. import { HandlebarsMjmlGenerator } from './handlebars-mjml-generator';
  8. import { NodemailerEmailSender } from './nodemailer-email-sender';
  9. import { TemplateLoader } from './template-loader';
  10. import {
  11. EmailDetails,
  12. EmailGenerator,
  13. EmailPluginOptions,
  14. EmailSender,
  15. EmailTransportOptions,
  16. IntermediateEmailDetails,
  17. } from './types';
  18. /**
  19. * This class combines the template loading, generation, and email sending - the actual "work" of
  20. * the EmailPlugin. It is arranged this way primarily to accommodate easier testing, so that the
  21. * tests can be run without needing all the JobQueue stuff which would require a full e2e test.
  22. */
  23. @Injectable()
  24. export class EmailProcessor {
  25. protected templateLoader: TemplateLoader;
  26. protected emailSender: EmailSender;
  27. protected generator: EmailGenerator;
  28. protected transport: EmailTransportOptions;
  29. constructor(@Inject(EMAIL_PLUGIN_OPTIONS) protected options: EmailPluginOptions) {}
  30. async init() {
  31. this.templateLoader = new TemplateLoader(this.options.templatePath);
  32. this.emailSender = this.options.emailSender ? this.options.emailSender : new NodemailerEmailSender();
  33. this.generator = this.options.emailGenerator
  34. ? this.options.emailGenerator
  35. : new HandlebarsMjmlGenerator();
  36. if (this.generator.onInit) {
  37. await this.generator.onInit.call(this.generator, this.options);
  38. }
  39. if (isDevModeOptions(this.options)) {
  40. this.transport = {
  41. type: 'file',
  42. raw: false,
  43. outputPath: this.options.outputPath,
  44. };
  45. } else {
  46. if (!this.options.transport) {
  47. throw new InternalServerError(
  48. `When devMode is not set to true, the 'transport' property must be set.`,
  49. );
  50. }
  51. this.transport = this.options.transport;
  52. }
  53. if (this.transport.type === 'file') {
  54. // ensure the configured directory exists before
  55. // we attempt to write files to it
  56. const emailPath = this.transport.outputPath;
  57. await fs.ensureDir(emailPath);
  58. }
  59. }
  60. async process(data: IntermediateEmailDetails) {
  61. try {
  62. const bodySource = await this.templateLoader.loadTemplate(data.type, data.templateFile);
  63. const generated = await this.generator.generate(
  64. data.from,
  65. data.subject,
  66. bodySource,
  67. data.templateVars,
  68. );
  69. const emailDetails: EmailDetails = {
  70. ...generated,
  71. recipient: data.recipient,
  72. attachments: deserializeAttachments(data.attachments),
  73. cc: data.cc,
  74. bcc: data.bcc,
  75. replyTo: data.replyTo,
  76. };
  77. await this.emailSender.send(emailDetails, this.transport);
  78. return true;
  79. } catch (err: unknown) {
  80. if (err instanceof Error) {
  81. Logger.error(err.message, loggerCtx, err.stack);
  82. } else {
  83. Logger.error(String(err), loggerCtx);
  84. }
  85. return false;
  86. }
  87. }
  88. }