mail.ts 5.0 KB

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