1 |
|
2 |
|
3 | const nodemailer = require('nodemailer')
|
4 | const path = require('path')
|
5 | const debug = require('../debug').email
|
6 |
|
7 | /**
|
8 | * Models a Nodemailer-based email sending service.
|
9 | *
|
10 | * @see https://nodemailer.com/about/
|
11 | */
|
12 | class EmailService {
|
13 | /**
|
14 | * @constructor
|
15 | *
|
16 | * @param templatePath {string} Path to the email templates directory
|
17 | *
|
18 | * @param config {Object} Nodemailer configuration object
|
19 | * @see https://nodemailer.com/smtp/
|
20 | *
|
21 | * Transport SMTP config options:
|
22 | * @param config.host {string} e.g. 'smtp.gmail.com'
|
23 | * @param config.port {string} e.g. '465'
|
24 | * @param config.secure {boolean} Whether to use TLS when connecting to server
|
25 | *
|
26 | * Transport authentication config options:
|
27 | * @param config.auth {Object}
|
28 | * @param config.auth.user {string} Smtp username (e.g. 'alice@gmail.com')
|
29 | * @param config.auth.pass {string} Smtp password
|
30 | *
|
31 | * Optional default Sender / `from:` address:
|
32 | * @param [config.sender] {string} e.g. 'Solid Server <no-reply@databox.me>'
|
33 | */
|
34 | constructor (templatePath, config) {
|
35 | this.mailer = nodemailer.createTransport(config)
|
36 |
|
37 | this.sender = this.initSender(config)
|
38 |
|
39 | this.templatePath = templatePath
|
40 | }
|
41 |
|
42 | /**
|
43 | * Returns the default Sender address based on config.
|
44 | *
|
45 | * Note that if using Gmail for SMTP transport, Gmail ignores the sender
|
46 | * `from:` address and uses the SMTP username instead (`auth.user`).
|
47 | *
|
48 | * @param config {Object}
|
49 | *
|
50 | * The sender is derived from either:
|
51 | * @param [config.sender] {string} e.g. 'Solid Server <no-reply@databox.me>'
|
52 | *
|
53 | * or, if explicit sender is not passed in, uses:
|
54 | * @param [config.host] {string} SMTP host from transport config
|
55 | *
|
56 | * @return {string} Sender `from:` address
|
57 | */
|
58 | initSender (config) {
|
59 | let sender
|
60 |
|
61 | if (config.sender) {
|
62 | sender = config.sender
|
63 | } else {
|
64 | sender = `no-reply@${config.host}`
|
65 | }
|
66 |
|
67 | return sender
|
68 | }
|
69 |
|
70 | /**
|
71 | * Sends an email (passes it through to nodemailer).
|
72 | *
|
73 | * @param email {Object}
|
74 | *
|
75 | * @return {Promise<EmailResponse>}
|
76 | */
|
77 | sendMail (email) {
|
78 | email.from = email.from || this.sender
|
79 |
|
80 | debug('Sending email to ' + email.to)
|
81 | return this.mailer.sendMail(email)
|
82 | }
|
83 |
|
84 | /**
|
85 | * Sends an email using a saved email template.
|
86 | * Usage:
|
87 | *
|
88 | * ```
|
89 | * let data = { webid: 'https://example.com/alice#me', ... }
|
90 | *
|
91 | * emailService.sendWithTemplate('welcome', data)
|
92 | * .then(response => {
|
93 | * // email sent using the 'welcome' template
|
94 | * })
|
95 | * ```
|
96 | *
|
97 | * @param templateName {string} Name of a template file in the email-templates
|
98 | * dir, no extension necessary.
|
99 | *
|
100 | * @param data {Object} Key/value hashmap of data for an email template.
|
101 | *
|
102 | * @return {Promise<EmailResponse>}
|
103 | */
|
104 | sendWithTemplate (templateName, data) {
|
105 | return Promise.resolve()
|
106 | .then(() => {
|
107 | let renderedEmail = this.emailFromTemplate(templateName, data)
|
108 |
|
109 | return this.sendMail(renderedEmail)
|
110 | })
|
111 | }
|
112 |
|
113 | /**
|
114 | * Returns an email from a rendered template.
|
115 | *
|
116 | * @param templateName {string}
|
117 | * @param data {Object} Key/value hashmap of data for an email template.
|
118 | *
|
119 | * @return {Object} Rendered email object from template
|
120 | */
|
121 | emailFromTemplate (templateName, data) {
|
122 | let template = this.readTemplate(templateName)
|
123 |
|
124 | return Object.assign({}, template.render(data), data)
|
125 | }
|
126 |
|
127 | /**
|
128 | * Reads (requires) and returns the contents of an email template file, for
|
129 | * a given template name.
|
130 | *
|
131 | * @param templateName {string}
|
132 | *
|
133 | * @throws {Error} If the template could not be found
|
134 | *
|
135 | * @return {Object}
|
136 | */
|
137 | readTemplate (templateName) {
|
138 | let templateFile = this.templatePathFor(templateName)
|
139 | let template
|
140 |
|
141 | try {
|
142 | template = require(templateFile)
|
143 | } catch (error) {
|
144 | throw new Error('Cannot find email template: ' + templateFile)
|
145 | }
|
146 |
|
147 | return template
|
148 | }
|
149 |
|
150 | /**
|
151 | * Returns a template file path for a given template name.
|
152 | *
|
153 | * @param templateName {string}
|
154 | *
|
155 | * @return {string}
|
156 | */
|
157 | templatePathFor (templateName) {
|
158 | return path.join(this.templatePath, templateName)
|
159 | }
|
160 | }
|
161 |
|
162 | module.exports = EmailService
|