diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-05-05 20:22:22 +0200 |
---|---|---|
committer | Rigel Kent <par@rigelk.eu> | 2020-05-08 15:31:51 +0200 |
commit | df4c603dea022146476812cbbc2b9f8f1e5e4870 (patch) | |
tree | c0d27576fb6711b4b64d2186e8dca3f04b9b1dfe /server/lib/emailer.ts | |
parent | 91b8e675e26dd65e1ebb23706cb16b3a3f8bcf73 (diff) | |
download | PeerTube-df4c603dea022146476812cbbc2b9f8f1e5e4870.tar.gz PeerTube-df4c603dea022146476812cbbc2b9f8f1e5e4870.tar.zst PeerTube-df4c603dea022146476812cbbc2b9f8f1e5e4870.zip |
Switch emails to pug templates and provide richer html/text-only versions
Diffstat (limited to 'server/lib/emailer.ts')
-rw-r--r-- | server/lib/emailer.ts | 392 |
1 files changed, 205 insertions, 187 deletions
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 45d57fd28..935c9e882 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { createTransport, Transporter } from 'nodemailer' | 1 | import { createTransport, Transporter } from 'nodemailer' |
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance, root } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 4 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
5 | import { JobQueue } from './job-queue' | 5 | import { JobQueue } from './job-queue' |
@@ -16,6 +16,12 @@ import { | |||
16 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' | 16 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' |
17 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' | 17 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' |
18 | import { EmailPayload } from '@shared/models' | 18 | import { EmailPayload } from '@shared/models' |
19 | import { join } from 'path' | ||
20 | import { VideoAbuse } from '../../shared/models/videos' | ||
21 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | ||
22 | import { merge } from 'lodash' | ||
23 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
24 | const Email = require('email-templates') | ||
19 | 25 | ||
20 | class Emailer { | 26 | class Emailer { |
21 | 27 | ||
@@ -105,37 +111,36 @@ class Emailer { | |||
105 | const channelName = video.VideoChannel.getDisplayName() | 111 | const channelName = video.VideoChannel.getDisplayName() |
106 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 112 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
107 | 113 | ||
108 | const text = 'Hi dear user,\n\n' + | ||
109 | `Your subscription ${channelName} just published a new video: ${video.name}` + | ||
110 | '\n\n' + | ||
111 | `You can view it on ${videoUrl} ` + | ||
112 | '\n\n' + | ||
113 | 'Cheers,\n' + | ||
114 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
115 | |||
116 | const emailPayload: EmailPayload = { | 114 | const emailPayload: EmailPayload = { |
117 | to, | 115 | to, |
118 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video', | 116 | subject: channelName + ' just published a new video', |
119 | text | 117 | text: `Your subscription ${channelName} just published a new video: "${video.name}".`, |
118 | locals: { | ||
119 | title: 'New content ', | ||
120 | action: { | ||
121 | text: 'View video', | ||
122 | url: videoUrl | ||
123 | } | ||
124 | } | ||
120 | } | 125 | } |
121 | 126 | ||
122 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 127 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
123 | } | 128 | } |
124 | 129 | ||
125 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { | 130 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { |
126 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() | ||
127 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() | 131 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() |
128 | 132 | ||
129 | const text = 'Hi dear user,\n\n' + | ||
130 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + | ||
131 | '\n\n' + | ||
132 | 'Cheers,\n' + | ||
133 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
134 | |||
135 | const emailPayload: EmailPayload = { | 133 | const emailPayload: EmailPayload = { |
134 | template: 'follower-on-channel', | ||
136 | to, | 135 | to, |
137 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName, | 136 | subject: `New follower on your channel ${followingName}`, |
138 | text | 137 | locals: { |
138 | followerName: actorFollow.ActorFollower.Account.getDisplayName(), | ||
139 | followerUrl: actorFollow.ActorFollower.url, | ||
140 | followingName, | ||
141 | followingUrl: actorFollow.ActorFollowing.url, | ||
142 | followType | ||
143 | } | ||
139 | } | 144 | } |
140 | 145 | ||
141 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 146 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -144,32 +149,28 @@ class Emailer { | |||
144 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { | 149 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { |
145 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | 150 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' |
146 | 151 | ||
147 | const text = 'Hi dear admin,\n\n' + | ||
148 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + | ||
149 | '\n\n' + | ||
150 | 'Cheers,\n' + | ||
151 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
152 | |||
153 | const emailPayload: EmailPayload = { | 152 | const emailPayload: EmailPayload = { |
154 | to, | 153 | to, |
155 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower', | 154 | subject: 'New instance follower', |
156 | text | 155 | text: `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}.`, |
156 | locals: { | ||
157 | title: 'New instance follower', | ||
158 | action: { | ||
159 | text: 'Review followers', | ||
160 | url: WEBSERVER.URL + '/admin/follows/followers-list' | ||
161 | } | ||
162 | } | ||
157 | } | 163 | } |
158 | 164 | ||
159 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 165 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
160 | } | 166 | } |
161 | 167 | ||
162 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { | 168 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { |
163 | const text = 'Hi dear admin,\n\n' + | 169 | const instanceUrl = actorFollow.ActorFollowing.url |
164 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + | ||
165 | '\n\n' + | ||
166 | 'Cheers,\n' + | ||
167 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
168 | |||
169 | const emailPayload: EmailPayload = { | 170 | const emailPayload: EmailPayload = { |
170 | to, | 171 | to, |
171 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following', | 172 | subject: 'Auto instance following', |
172 | text | 173 | text: `Your instance automatically followed a new instance: <a href="${instanceUrl}">${instanceUrl}</a>.` |
173 | } | 174 | } |
174 | 175 | ||
175 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 176 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -178,18 +179,17 @@ class Emailer { | |||
178 | myVideoPublishedNotification (to: string[], video: MVideo) { | 179 | myVideoPublishedNotification (to: string[], video: MVideo) { |
179 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 180 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
180 | 181 | ||
181 | const text = 'Hi dear user,\n\n' + | ||
182 | `Your video ${video.name} has been published.` + | ||
183 | '\n\n' + | ||
184 | `You can view it on ${videoUrl} ` + | ||
185 | '\n\n' + | ||
186 | 'Cheers,\n' + | ||
187 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
188 | |||
189 | const emailPayload: EmailPayload = { | 182 | const emailPayload: EmailPayload = { |
190 | to, | 183 | to, |
191 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`, | 184 | subject: `Your video ${video.name} has been published`, |
192 | text | 185 | text: `Your video "${video.name}" has been published.`, |
186 | locals: { | ||
187 | title: 'You video is live', | ||
188 | action: { | ||
189 | text: 'View video', | ||
190 | url: videoUrl | ||
191 | } | ||
192 | } | ||
193 | } | 193 | } |
194 | 194 | ||
195 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 195 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -198,18 +198,17 @@ class Emailer { | |||
198 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { | 198 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { |
199 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 199 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
200 | 200 | ||
201 | const text = 'Hi dear user,\n\n' + | ||
202 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + | ||
203 | '\n\n' + | ||
204 | `You can view the imported video on ${videoUrl} ` + | ||
205 | '\n\n' + | ||
206 | 'Cheers,\n' + | ||
207 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
208 | |||
209 | const emailPayload: EmailPayload = { | 201 | const emailPayload: EmailPayload = { |
210 | to, | 202 | to, |
211 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`, | 203 | subject: `Your video import ${videoImport.getTargetIdentifier()} is complete`, |
212 | text | 204 | text: `Your video "${videoImport.getTargetIdentifier()}" just finished importing.`, |
205 | locals: { | ||
206 | title: 'Import complete', | ||
207 | action: { | ||
208 | text: 'View video', | ||
209 | url: videoUrl | ||
210 | } | ||
211 | } | ||
213 | } | 212 | } |
214 | 213 | ||
215 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 214 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -218,40 +217,47 @@ class Emailer { | |||
218 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { | 217 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { |
219 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' | 218 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
220 | 219 | ||
221 | const text = 'Hi dear user,\n\n' + | 220 | const text = |
222 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + | 221 | `Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` + |
223 | '\n\n' + | ||
224 | `See your videos import dashboard for more information: ${importUrl}` + | ||
225 | '\n\n' + | 222 | '\n\n' + |
226 | 'Cheers,\n' + | 223 | `See your videos import dashboard for more information: <a href="${importUrl}">${importUrl}</a>.` |
227 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
228 | 224 | ||
229 | const emailPayload: EmailPayload = { | 225 | const emailPayload: EmailPayload = { |
230 | to, | 226 | to, |
231 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, | 227 | subject: `Your video import "${videoImport.getTargetIdentifier()}" encountered an error`, |
232 | text | 228 | text, |
229 | locals: { | ||
230 | title: 'Import failed', | ||
231 | action: { | ||
232 | text: 'Review imports', | ||
233 | url: importUrl | ||
234 | } | ||
235 | } | ||
233 | } | 236 | } |
234 | 237 | ||
235 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 238 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
236 | } | 239 | } |
237 | 240 | ||
238 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { | 241 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { |
239 | const accountName = comment.Account.getDisplayName() | ||
240 | const video = comment.Video | 242 | const video = comment.Video |
243 | const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() | ||
241 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 244 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
242 | 245 | ||
243 | const text = 'Hi dear user,\n\n' + | ||
244 | `A new comment has been posted by ${accountName} on your video ${video.name}` + | ||
245 | '\n\n' + | ||
246 | `You can view it on ${commentUrl} ` + | ||
247 | '\n\n' + | ||
248 | 'Cheers,\n' + | ||
249 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
250 | |||
251 | const emailPayload: EmailPayload = { | 246 | const emailPayload: EmailPayload = { |
247 | template: 'video-comment-new', | ||
252 | to, | 248 | to, |
253 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name, | 249 | subject: 'New comment on your video ' + video.name, |
254 | text | 250 | locals: { |
251 | accountName: comment.Account.getDisplayName(), | ||
252 | accountUrl: comment.Account.Actor.url, | ||
253 | comment, | ||
254 | video, | ||
255 | videoUrl, | ||
256 | action: { | ||
257 | text: 'View comment', | ||
258 | url: commentUrl | ||
259 | } | ||
260 | } | ||
255 | } | 261 | } |
256 | 262 | ||
257 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 263 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -260,75 +266,88 @@ class Emailer { | |||
260 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { | 266 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { |
261 | const accountName = comment.Account.getDisplayName() | 267 | const accountName = comment.Account.getDisplayName() |
262 | const video = comment.Video | 268 | const video = comment.Video |
269 | const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() | ||
263 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 270 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
264 | 271 | ||
265 | const text = 'Hi dear user,\n\n' + | ||
266 | `${accountName} mentioned you on video ${video.name}` + | ||
267 | '\n\n' + | ||
268 | `You can view the comment on ${commentUrl} ` + | ||
269 | '\n\n' + | ||
270 | 'Cheers,\n' + | ||
271 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
272 | |||
273 | const emailPayload: EmailPayload = { | 272 | const emailPayload: EmailPayload = { |
273 | template: 'video-comment-mention', | ||
274 | to, | 274 | to, |
275 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name, | 275 | subject: 'Mention on video ' + video.name, |
276 | text | 276 | locals: { |
277 | comment, | ||
278 | video, | ||
279 | videoUrl, | ||
280 | accountName, | ||
281 | action: { | ||
282 | text: 'View comment', | ||
283 | url: commentUrl | ||
284 | } | ||
285 | } | ||
277 | } | 286 | } |
278 | 287 | ||
279 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
280 | } | 289 | } |
281 | 290 | ||
282 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { | 291 | addVideoAbuseModeratorsNotification (to: string[], parameters: { |
283 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 292 | videoAbuse: VideoAbuse |
284 | 293 | videoAbuseInstance: MVideoAbuseVideo | |
285 | const text = 'Hi,\n\n' + | 294 | reporter: string |
286 | `${WEBSERVER.HOST} received an abuse for the following video: ${videoUrl}\n\n` + | 295 | }) { |
287 | 'Cheers,\n' + | 296 | const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id |
288 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 297 | const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() |
289 | 298 | ||
290 | const emailPayload: EmailPayload = { | 299 | const emailPayload: EmailPayload = { |
300 | template: 'video-abuse-new', | ||
291 | to, | 301 | to, |
292 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse', | 302 | subject: `New video abuse report from ${parameters.reporter}`, |
293 | text | 303 | locals: { |
304 | videoUrl, | ||
305 | videoAbuseUrl, | ||
306 | videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), | ||
307 | videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), | ||
308 | videoAbuse: parameters.videoAbuse, | ||
309 | reporter: parameters.reporter, | ||
310 | action: { | ||
311 | text: 'View report #' + parameters.videoAbuse.id, | ||
312 | url: videoAbuseUrl | ||
313 | } | ||
314 | } | ||
294 | } | 315 | } |
295 | 316 | ||
296 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 317 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
297 | } | 318 | } |
298 | 319 | ||
299 | addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { | 320 | async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { |
300 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 321 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
301 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 322 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
302 | 323 | const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON() | |
303 | const text = 'Hi,\n\n' + | ||
304 | 'A recently added video was auto-blacklisted and requires moderator review before publishing.' + | ||
305 | '\n\n' + | ||
306 | `You can view it and take appropriate action on ${videoUrl}` + | ||
307 | '\n\n' + | ||
308 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | ||
309 | '\n\n' + | ||
310 | 'Cheers,\n' + | ||
311 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
312 | 324 | ||
313 | const emailPayload: EmailPayload = { | 325 | const emailPayload: EmailPayload = { |
326 | template: 'video-auto-blacklist-new', | ||
314 | to, | 327 | to, |
315 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review', | 328 | subject: 'A new video is pending moderation', |
316 | text | 329 | locals: { |
330 | channel, | ||
331 | videoUrl, | ||
332 | videoName: videoBlacklist.Video.name, | ||
333 | action: { | ||
334 | text: 'Review autoblacklist', | ||
335 | url: VIDEO_AUTO_BLACKLIST_URL | ||
336 | } | ||
337 | } | ||
317 | } | 338 | } |
318 | 339 | ||
319 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 340 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
320 | } | 341 | } |
321 | 342 | ||
322 | addNewUserRegistrationNotification (to: string[], user: MUser) { | 343 | addNewUserRegistrationNotification (to: string[], user: MUser) { |
323 | const text = 'Hi,\n\n' + | ||
324 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | ||
325 | 'Cheers,\n' + | ||
326 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
327 | |||
328 | const emailPayload: EmailPayload = { | 344 | const emailPayload: EmailPayload = { |
345 | template: 'user-registered', | ||
329 | to, | 346 | to, |
330 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST, | 347 | subject: `a new user registered on ${WEBSERVER.HOST}: ${user.username}`, |
331 | text | 348 | locals: { |
349 | user | ||
350 | } | ||
332 | } | 351 | } |
333 | 352 | ||
334 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 353 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -341,16 +360,13 @@ class Emailer { | |||
341 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' | 360 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' |
342 | const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` | 361 | const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` |
343 | 362 | ||
344 | const text = 'Hi,\n\n' + | ||
345 | blockedString + | ||
346 | '\n\n' + | ||
347 | 'Cheers,\n' + | ||
348 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
349 | |||
350 | const emailPayload: EmailPayload = { | 363 | const emailPayload: EmailPayload = { |
351 | to, | 364 | to, |
352 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`, | 365 | subject: `Video ${videoName} blacklisted`, |
353 | text | 366 | text: blockedString, |
367 | locals: { | ||
368 | title: 'Your video was blacklisted' | ||
369 | } | ||
354 | } | 370 | } |
355 | 371 | ||
356 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 372 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -359,66 +375,53 @@ class Emailer { | |||
359 | addVideoUnblacklistNotification (to: string[], video: MVideo) { | 375 | addVideoUnblacklistNotification (to: string[], video: MVideo) { |
360 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 376 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
361 | 377 | ||
362 | const text = 'Hi,\n\n' + | ||
363 | `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + | ||
364 | '\n\n' + | ||
365 | 'Cheers,\n' + | ||
366 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
367 | |||
368 | const emailPayload: EmailPayload = { | 378 | const emailPayload: EmailPayload = { |
369 | to, | 379 | to, |
370 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`, | 380 | subject: `Video ${video.name} unblacklisted`, |
371 | text | 381 | text: `Your video "${video.name}" (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.`, |
382 | locals: { | ||
383 | title: 'Your video was unblacklisted' | ||
384 | } | ||
372 | } | 385 | } |
373 | 386 | ||
374 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 387 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
375 | } | 388 | } |
376 | 389 | ||
377 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { | 390 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { |
378 | const text = 'Hi dear user,\n\n' + | ||
379 | `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + | ||
380 | `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + | ||
381 | 'If you are not the person who initiated this request, please ignore this email.\n\n' + | ||
382 | 'Cheers,\n' + | ||
383 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
384 | |||
385 | const emailPayload: EmailPayload = { | 391 | const emailPayload: EmailPayload = { |
392 | template: 'password-reset', | ||
386 | to: [ to ], | 393 | to: [ to ], |
387 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password', | 394 | subject: 'Reset your account password', |
388 | text | 395 | locals: { |
396 | resetPasswordUrl | ||
397 | } | ||
389 | } | 398 | } |
390 | 399 | ||
391 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
392 | } | 401 | } |
393 | 402 | ||
394 | addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) { | 403 | addPasswordCreateEmailJob (username: string, to: string, createPasswordUrl: string) { |
395 | const text = 'Hi,\n\n' + | ||
396 | `Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` + | ||
397 | `Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` + | ||
398 | 'Cheers,\n' + | ||
399 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
400 | |||
401 | const emailPayload: EmailPayload = { | 404 | const emailPayload: EmailPayload = { |
405 | template: 'password-create', | ||
402 | to: [ to ], | 406 | to: [ to ], |
403 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password', | 407 | subject: 'Create your account password', |
404 | text | 408 | locals: { |
409 | username, | ||
410 | createPasswordUrl | ||
411 | } | ||
405 | } | 412 | } |
406 | 413 | ||
407 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 414 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
408 | } | 415 | } |
409 | 416 | ||
410 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 417 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { |
411 | const text = 'Welcome to PeerTube,\n\n' + | ||
412 | `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + | ||
413 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | ||
414 | 'If you are not the person who initiated this request, please ignore this email.\n\n' + | ||
415 | 'Cheers,\n' + | ||
416 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
417 | |||
418 | const emailPayload: EmailPayload = { | 418 | const emailPayload: EmailPayload = { |
419 | template: 'verify-email', | ||
419 | to: [ to ], | 420 | to: [ to ], |
420 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email', | 421 | subject: `Verify your email on ${WEBSERVER.HOST}`, |
421 | text | 422 | locals: { |
423 | verifyEmailUrl | ||
424 | } | ||
422 | } | 425 | } |
423 | 426 | ||
424 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 427 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -427,39 +430,28 @@ class Emailer { | |||
427 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { | 430 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { |
428 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 431 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
429 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 432 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
430 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` | ||
431 | |||
432 | const text = 'Hi,\n\n' + | ||
433 | blockedString + | ||
434 | '\n\n' + | ||
435 | 'Cheers,\n' + | ||
436 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
437 | 433 | ||
438 | const to = user.email | 434 | const to = user.email |
439 | const emailPayload: EmailPayload = { | 435 | const emailPayload: EmailPayload = { |
440 | to: [ to ], | 436 | to: [ to ], |
441 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord, | 437 | subject: 'Account ' + blockedWord, |
442 | text | 438 | text: `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` |
443 | } | 439 | } |
444 | 440 | ||
445 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 441 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
446 | } | 442 | } |
447 | 443 | ||
448 | addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) { | 444 | addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) { |
449 | const text = 'Hello dear admin,\n\n' + | ||
450 | fromName + ' sent you a message' + | ||
451 | '\n\n---------------------------------------\n\n' + | ||
452 | body + | ||
453 | '\n\n---------------------------------------\n\n' + | ||
454 | 'Cheers,\n' + | ||
455 | 'PeerTube.' | ||
456 | |||
457 | const emailPayload: EmailPayload = { | 445 | const emailPayload: EmailPayload = { |
458 | fromDisplayName: fromEmail, | 446 | template: 'contact-form', |
459 | replyTo: fromEmail, | ||
460 | to: [ CONFIG.ADMIN.EMAIL ], | 447 | to: [ CONFIG.ADMIN.EMAIL ], |
461 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject, | 448 | replyTo: `"${fromName}" <${fromEmail}>`, |
462 | text | 449 | subject: `(contact form) ${subject}`, |
450 | locals: { | ||
451 | fromName, | ||
452 | fromEmail, | ||
453 | body | ||
454 | } | ||
463 | } | 455 | } |
464 | 456 | ||
465 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 457 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -470,18 +462,44 @@ class Emailer { | |||
470 | throw new Error('Cannot send mail because SMTP is not configured.') | 462 | throw new Error('Cannot send mail because SMTP is not configured.') |
471 | } | 463 | } |
472 | 464 | ||
473 | const fromDisplayName = options.fromDisplayName | 465 | const fromDisplayName = options.from |
474 | ? options.fromDisplayName | 466 | ? options.from |
475 | : WEBSERVER.HOST | 467 | : WEBSERVER.HOST |
476 | 468 | ||
469 | const email = new Email({ | ||
470 | send: true, | ||
471 | message: { | ||
472 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>` | ||
473 | }, | ||
474 | transport: this.transporter, | ||
475 | views: { | ||
476 | root: join(root(), 'server', 'lib', 'emails') | ||
477 | }, | ||
478 | subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX | ||
479 | }) | ||
480 | |||
477 | for (const to of options.to) { | 481 | for (const to of options.to) { |
478 | await this.transporter.sendMail({ | 482 | await email |
479 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`, | 483 | .send(merge( |
480 | replyTo: options.replyTo, | 484 | { |
481 | to, | 485 | template: 'common', |
482 | subject: options.subject, | 486 | message: { |
483 | text: options.text | 487 | to, |
484 | }) | 488 | from: options.from, |
489 | subject: options.subject, | ||
490 | replyTo: options.replyTo | ||
491 | }, | ||
492 | locals: { // default variables available in all templates | ||
493 | WEBSERVER, | ||
494 | EMAIL: CONFIG.EMAIL, | ||
495 | text: options.text, | ||
496 | subject: options.subject | ||
497 | } | ||
498 | }, | ||
499 | options // overriden/new variables given for a specific template in the payload | ||
500 | ) as SendEmailOptions) | ||
501 | .then(logger.info) | ||
502 | .catch(logger.error) | ||
485 | } | 503 | } |
486 | } | 504 | } |
487 | 505 | ||