email-processor.ts 4.5 KB

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