mail.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import nodemailer from 'nodemailer';
  2. import swig from 'swig-templates';
  3. import loggerFactory from '~/utils/logger';
  4. import S2sMessage from '../models/vo/s2s-message';
  5. import { S2sMessageHandlable } from './s2s-messaging/handlable';
  6. const logger = loggerFactory('growi:service:mail');
  7. type MailConfig = {
  8. to?: string,
  9. from?: string,
  10. text?: string,
  11. subject?: string,
  12. }
  13. class MailService implements S2sMessageHandlable {
  14. appService!: any;
  15. configManager!: any;
  16. s2sMessagingService!: any;
  17. mailConfig: MailConfig = {};
  18. mailer: any = {};
  19. lastLoadedAt?: Date;
  20. /**
  21. * the flag whether mailer is set up successfully
  22. */
  23. isMailerSetup = false;
  24. constructor(crowi) {
  25. this.appService = crowi.appService;
  26. this.configManager = crowi.configManager;
  27. this.s2sMessagingService = crowi.s2sMessagingService;
  28. this.initialize();
  29. }
  30. /**
  31. * @inheritdoc
  32. */
  33. shouldHandleS2sMessage(s2sMessage) {
  34. const { eventName, updatedAt } = s2sMessage;
  35. if (eventName !== 'mailServiceUpdated' || updatedAt == null) {
  36. return false;
  37. }
  38. return this.lastLoadedAt == null || this.lastLoadedAt < new Date(s2sMessage.updatedAt);
  39. }
  40. /**
  41. * @inheritdoc
  42. */
  43. async handleS2sMessage(s2sMessage) {
  44. const { configManager } = this;
  45. logger.info('Initialize mail settings by pubsub notification');
  46. await configManager.loadConfigs();
  47. this.initialize();
  48. }
  49. async publishUpdatedMessage() {
  50. const { s2sMessagingService } = this;
  51. if (s2sMessagingService != null) {
  52. const s2sMessage = new S2sMessage('mailServiceUpdated', { updatedAt: new Date() });
  53. try {
  54. await s2sMessagingService.publish(s2sMessage);
  55. }
  56. catch (e) {
  57. logger.error('Failed to publish update message with S2sMessagingService: ', e.message);
  58. }
  59. }
  60. }
  61. initialize() {
  62. const { appService, configManager } = this;
  63. this.isMailerSetup = false;
  64. if (!configManager.getConfig('crowi', 'mail:from')) {
  65. this.mailer = null;
  66. return;
  67. }
  68. const transmissionMethod = configManager.getConfig('crowi', 'mail:transmissionMethod');
  69. if (transmissionMethod === 'smtp') {
  70. this.mailer = this.createSMTPClient();
  71. }
  72. else if (transmissionMethod === 'ses') {
  73. this.mailer = this.createSESClient();
  74. }
  75. else {
  76. this.mailer = null;
  77. }
  78. if (this.mailer != null) {
  79. this.isMailerSetup = true;
  80. }
  81. this.mailConfig.from = configManager.getConfig('crowi', 'mail:from');
  82. this.mailConfig.subject = `${appService.getAppTitle()}からのメール`;
  83. logger.debug('mailer initialized');
  84. }
  85. createSMTPClient(option?) {
  86. const { configManager } = this;
  87. logger.debug('createSMTPClient option', option);
  88. if (!option) {
  89. const host = configManager.getConfig('crowi', 'mail:smtpHost');
  90. const port = configManager.getConfig('crowi', 'mail:smtpPort');
  91. if (host == null || port == null) {
  92. return null;
  93. }
  94. option = { // eslint-disable-line no-param-reassign
  95. host,
  96. port,
  97. };
  98. if (configManager.getConfig('crowi', 'mail:smtpUser') && configManager.getConfig('crowi', 'mail:smtpPassword')) {
  99. option.auth = {
  100. user: configManager.getConfig('crowi', 'mail:smtpUser'),
  101. pass: configManager.getConfig('crowi', 'mail:smtpPassword'),
  102. };
  103. }
  104. if (option.port === 465) {
  105. option.secure = true;
  106. }
  107. }
  108. option.tls = { rejectUnauthorized: false };
  109. const client = nodemailer.createTransport(option);
  110. logger.debug('mailer set up for SMTP', client);
  111. return client;
  112. }
  113. createSESClient(option?) {
  114. const { configManager } = this;
  115. if (!option) {
  116. const accessKeyId = configManager.getConfig('crowi', 'mail:sesAccessKeyId');
  117. const secretAccessKey = configManager.getConfig('crowi', 'mail:sesSecretAccessKey');
  118. if (accessKeyId == null || secretAccessKey == null) {
  119. return null;
  120. }
  121. option = { // eslint-disable-line no-param-reassign
  122. accessKeyId,
  123. secretAccessKey,
  124. };
  125. }
  126. const ses = require('nodemailer-ses-transport');
  127. const client = nodemailer.createTransport(ses(option));
  128. logger.debug('mailer set up for SES', client);
  129. return client;
  130. }
  131. setupMailConfig(overrideConfig) {
  132. const c = overrideConfig;
  133. let mc: MailConfig = {};
  134. mc = this.mailConfig;
  135. mc.to = c.to;
  136. mc.from = c.from || this.mailConfig.from;
  137. mc.text = c.text;
  138. mc.subject = c.subject || this.mailConfig.subject;
  139. return mc;
  140. }
  141. async send(config) {
  142. if (this.mailer == null) {
  143. throw new Error('Mailer is not completed to set up. Please set up SMTP or AWS setting.');
  144. }
  145. const templateVars = config.vars || {};
  146. const output = await swig.renderFile(
  147. config.template,
  148. templateVars,
  149. );
  150. config.text = output;
  151. return this.mailer.sendMail(this.setupMailConfig(config));
  152. }
  153. }
  154. module.exports = MailService;