mail.ts 5.1 KB

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