email-processor.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { Inject, Injectable } from '@nestjs/common';
  2. import { ModuleRef } from '@nestjs/core';
  3. import { EventBus, Injector, 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 { EmailSendEvent } from './email-send-event';
  9. import { EmailGenerator } from './generator/email-generator';
  10. import { HandlebarsMjmlGenerator } from './generator/handlebars-mjml-generator';
  11. import { EmailSender } from './sender/email-sender';
  12. import { NodemailerEmailSender } from './sender/nodemailer-email-sender';
  13. import {
  14. EmailDetails,
  15. EmailTransportOptions,
  16. InitializedEmailPluginOptions,
  17. IntermediateEmailDetails,
  18. } from './types';
  19. /**
  20. * This class combines the template loading, generation, and email sending - the actual "work" of
  21. * the EmailPlugin. It is arranged this way primarily to accommodate easier testing, so that the
  22. * tests can be run without needing all the JobQueue stuff which would require a full e2e test.
  23. */
  24. @Injectable()
  25. export class EmailProcessor {
  26. protected emailSender: EmailSender;
  27. protected generator: EmailGenerator;
  28. constructor(
  29. @Inject(EMAIL_PLUGIN_OPTIONS) protected options: InitializedEmailPluginOptions,
  30. private moduleRef: ModuleRef,
  31. private eventBus: EventBus,
  32. ) {}
  33. async init() {
  34. this.emailSender = this.options.emailSender ? this.options.emailSender : new NodemailerEmailSender();
  35. this.generator = this.options.emailGenerator
  36. ? this.options.emailGenerator
  37. : new HandlebarsMjmlGenerator();
  38. if (this.generator.onInit) {
  39. await this.generator.onInit.call(this.generator, this.options);
  40. }
  41. const transport = await this.getTransportSettings();
  42. if (transport.type === 'file') {
  43. // ensure the configured directory exists before
  44. // we attempt to write files to it
  45. const emailPath = transport.outputPath;
  46. await fs.ensureDir(emailPath);
  47. }
  48. }
  49. async process(data: IntermediateEmailDetails) {
  50. const ctx = RequestContext.deserialize(data.ctx);
  51. let emailDetails: EmailDetails = {} as any;
  52. try {
  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. 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. await this.eventBus.publish(new EmailSendEvent(ctx, emailDetails, true));
  74. return true;
  75. } catch (err: unknown) {
  76. if (err instanceof Error) {
  77. Logger.error(err.message, loggerCtx, err.stack);
  78. } else {
  79. Logger.error(String(err), loggerCtx);
  80. }
  81. await this.eventBus.publish(new EmailSendEvent(ctx, emailDetails, false, err as Error));
  82. throw err;
  83. }
  84. }
  85. async getTransportSettings(ctx?: RequestContext): Promise<EmailTransportOptions> {
  86. const transport = await resolveTransportSettings(this.options, new Injector(this.moduleRef), ctx);
  87. if (isDevModeOptions(this.options)) {
  88. if (transport && transport.type !== 'file') {
  89. Logger.warn(
  90. `The EmailPlugin is running in dev mode. The configured '${transport.type}' transport will be replaced by the 'file' transport.`,
  91. loggerCtx,
  92. );
  93. }
  94. return {
  95. type: 'file',
  96. raw: false,
  97. outputPath: this.options.outputPath,
  98. };
  99. } else {
  100. return transport;
  101. }
  102. }
  103. }