email-processor.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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. constructor(
  31. @Inject(EMAIL_PLUGIN_OPTIONS) protected options: InitializedEmailPluginOptions,
  32. private moduleRef: ModuleRef,
  33. ) {}
  34. async init() {
  35. this.emailSender = this.options.emailSender ? this.options.emailSender : new NodemailerEmailSender();
  36. this.generator = this.options.emailGenerator
  37. ? this.options.emailGenerator
  38. : new HandlebarsMjmlGenerator();
  39. if (this.generator.onInit) {
  40. await this.generator.onInit.call(this.generator, this.options);
  41. }
  42. const transport = await this.getTransportSettings();
  43. if (transport.type === 'file') {
  44. // ensure the configured directory exists before
  45. // we attempt to write files to it
  46. const emailPath = transport.outputPath;
  47. await fs.ensureDir(emailPath);
  48. }
  49. }
  50. async process(data: IntermediateEmailDetails) {
  51. try {
  52. const ctx = RequestContext.deserialize(data.ctx);
  53. const bodySource = await this.options.templateLoader.loadTemplate(
  54. new Injector(this.moduleRef),
  55. ctx,
  56. {
  57. templateName: data.templateFile,
  58. type: data.type,
  59. templateVars: data.templateVars,
  60. },
  61. );
  62. const generated = this.generator.generate(data.from, data.subject, bodySource, data.templateVars);
  63. const emailDetails: EmailDetails = {
  64. ...generated,
  65. recipient: data.recipient,
  66. attachments: deserializeAttachments(data.attachments),
  67. cc: data.cc,
  68. bcc: data.bcc,
  69. replyTo: data.replyTo,
  70. };
  71. const transportSettings = await this.getTransportSettings(ctx);
  72. await this.emailSender.send(emailDetails, transportSettings);
  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. async getTransportSettings(ctx?: RequestContext): Promise<EmailTransportOptions> {
  84. const transport = await resolveTransportSettings(this.options, new Injector(this.moduleRef), ctx);
  85. if (isDevModeOptions(this.options)) {
  86. if (transport && transport.type !== 'file') {
  87. Logger.warn(
  88. `The EmailPlugin is running in dev mode. The configured '${transport.type}' transport will be replaced by the 'file' transport.`,
  89. loggerCtx,
  90. );
  91. }
  92. return {
  93. type: 'file',
  94. raw: false,
  95. outputPath: this.options.outputPath,
  96. };
  97. } else {
  98. return transport;
  99. }
  100. }
  101. }