email-processor.ts 3.5 KB

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