]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/emailer.ts
Update CHANGELOG.md
[github/Chocobozzz/PeerTube.git] / server / lib / emailer.ts
CommitLineData
ecb4e35f
C
1import { createTransport, Transporter } from 'nodemailer'
2import { isTestInstance } from '../helpers/core-utils'
05e67d62 3import { bunyanLogger, logger } from '../helpers/logger'
ecb4e35f 4import { CONFIG } from '../initializers'
ba75d268
C
5import { UserModel } from '../models/account/user'
6import { VideoModel } from '../models/video/video'
ecb4e35f
C
7import { JobQueue } from './job-queue'
8import { EmailPayload } from './job-queue/handlers/email'
c9d5c64f 9import { readFileSync } from 'fs-extra'
cef534ed
C
10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
dc133480 13import { VideoImportModel } from '../models/video/video-import'
f7cc67b4 14import { ActorFollowModel } from '../models/activitypub/actor-follow'
ecb4e35f
C
15
16class Emailer {
17
18 private static instance: Emailer
19 private initialized = false
20 private transporter: Transporter
3b3b1820 21 private enabled = false
ecb4e35f
C
22
23 private constructor () {}
24
25 init () {
26 // Already initialized
27 if (this.initialized === true) return
28 this.initialized = true
29
30 if (CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) {
31 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
32
33 let tls
34 if (CONFIG.SMTP.CA_FILE) {
35 tls = {
36 ca: [ readFileSync(CONFIG.SMTP.CA_FILE) ]
37 }
38 }
39
f076daa7
C
40 let auth
41 if (CONFIG.SMTP.USERNAME && CONFIG.SMTP.PASSWORD) {
42 auth = {
43 user: CONFIG.SMTP.USERNAME,
44 pass: CONFIG.SMTP.PASSWORD
45 }
46 }
47
ecb4e35f
C
48 this.transporter = createTransport({
49 host: CONFIG.SMTP.HOSTNAME,
50 port: CONFIG.SMTP.PORT,
51 secure: CONFIG.SMTP.TLS,
05e67d62
C
52 debug: CONFIG.LOG.LEVEL === 'debug',
53 logger: bunyanLogger as any,
bebf2d89 54 ignoreTLS: CONFIG.SMTP.DISABLE_STARTTLS,
ecb4e35f 55 tls,
f076daa7 56 auth
ecb4e35f 57 })
3b3b1820
C
58
59 this.enabled = true
ecb4e35f
C
60 } else {
61 if (!isTestInstance()) {
62 logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!')
63 }
64 }
65 }
66
3b3b1820
C
67 isEnabled () {
68 return this.enabled
69 }
70
ecb4e35f
C
71 async checkConnectionOrDie () {
72 if (!this.transporter) return
73
3d3441d6
C
74 logger.info('Testing SMTP server...')
75
ecb4e35f
C
76 try {
77 const success = await this.transporter.verify()
78 if (success !== true) this.dieOnConnectionFailure()
79
80 logger.info('Successfully connected to SMTP server.')
81 } catch (err) {
82 this.dieOnConnectionFailure(err)
83 }
84 }
85
cef534ed
C
86 addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
87 const channelName = video.VideoChannel.getDisplayName()
88 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
89
ecb4e35f 90 const text = `Hi dear user,\n\n` +
cef534ed
C
91 `Your subscription ${channelName} just published a new video: ${video.name}` +
92 `\n\n` +
93 `You can view it on ${videoUrl} ` +
94 `\n\n` +
ecb4e35f
C
95 `Cheers,\n` +
96 `PeerTube.`
97
98 const emailPayload: EmailPayload = {
cef534ed
C
99 to,
100 subject: channelName + ' just published a new video',
ecb4e35f
C
101 text
102 }
103
104 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
105 }
106
f7cc67b4
C
107 addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') {
108 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
109 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
110
111 const text = `Hi dear user,\n\n` +
112 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
113 `\n\n` +
114 `Cheers,\n` +
115 `PeerTube.`
116
117 const emailPayload: EmailPayload = {
118 to,
119 subject: 'New follower on your channel ' + followingName,
120 text
121 }
122
123 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
124 }
125
dc133480
C
126 myVideoPublishedNotification (to: string[], video: VideoModel) {
127 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
128
129 const text = `Hi dear user,\n\n` +
130 `Your video ${video.name} has been published.` +
131 `\n\n` +
132 `You can view it on ${videoUrl} ` +
133 `\n\n` +
134 `Cheers,\n` +
135 `PeerTube.`
136
137 const emailPayload: EmailPayload = {
138 to,
139 subject: `Your video ${video.name} is published`,
140 text
141 }
142
143 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
144 }
145
146 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
147 const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
148
149 const text = `Hi dear user,\n\n` +
150 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
151 `\n\n` +
152 `You can view the imported video on ${videoUrl} ` +
153 `\n\n` +
154 `Cheers,\n` +
155 `PeerTube.`
156
157 const emailPayload: EmailPayload = {
158 to,
159 subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
160 text
161 }
162
163 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
164 }
165
166 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
167 const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
168
169 const text = `Hi dear user,\n\n` +
170 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
171 `\n\n` +
172 `See your videos import dashboard for more information: ${importUrl}` +
173 `\n\n` +
174 `Cheers,\n` +
175 `PeerTube.`
176
177 const emailPayload: EmailPayload = {
178 to,
179 subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
180 text
181 }
182
183 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
184 }
185
cef534ed
C
186 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
187 const accountName = comment.Account.getDisplayName()
188 const video = comment.Video
189 const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
190
191 const text = `Hi dear user,\n\n` +
192 `A new comment has been posted by ${accountName} on your video ${video.name}` +
193 `\n\n` +
194 `You can view it on ${commentUrl} ` +
195 `\n\n` +
d9eaee39
JM
196 `Cheers,\n` +
197 `PeerTube.`
198
199 const emailPayload: EmailPayload = {
cef534ed
C
200 to,
201 subject: 'New comment on your video ' + video.name,
d9eaee39
JM
202 text
203 }
204
205 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
206 }
207
f7cc67b4
C
208 addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) {
209 const accountName = comment.Account.getDisplayName()
210 const video = comment.Video
211 const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
212
213 const text = `Hi dear user,\n\n` +
214 `${accountName} mentioned you on video ${video.name}` +
215 `\n\n` +
216 `You can view the comment on ${commentUrl} ` +
217 `\n\n` +
218 `Cheers,\n` +
219 `PeerTube.`
220
221 const emailPayload: EmailPayload = {
222 to,
223 subject: 'Mention on video ' + video.name,
224 text
225 }
226
227 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
228 }
229
230 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
cef534ed 231 const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
ba75d268
C
232
233 const text = `Hi,\n\n` +
cef534ed 234 `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
ba75d268
C
235 `Cheers,\n` +
236 `PeerTube.`
237
ba75d268
C
238 const emailPayload: EmailPayload = {
239 to,
240 subject: '[PeerTube] Received a video abuse',
241 text
242 }
243
244 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
245 }
246
f7cc67b4
C
247 addNewUserRegistrationNotification (to: string[], user: UserModel) {
248 const text = `Hi,\n\n` +
249 `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` +
250 `Cheers,\n` +
251 `PeerTube.`
252
253 const emailPayload: EmailPayload = {
254 to,
255 subject: '[PeerTube] New user registration on ' + CONFIG.WEBSERVER.HOST,
256 text
257 }
258
259 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
260 }
261
262 addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
cef534ed
C
263 const videoName = videoBlacklist.Video.name
264 const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
26b7305a 265
cef534ed
C
266 const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
267 const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
26b7305a
C
268
269 const text = 'Hi,\n\n' +
270 blockedString +
271 '\n\n' +
272 'Cheers,\n' +
273 `PeerTube.`
274
26b7305a 275 const emailPayload: EmailPayload = {
cef534ed
C
276 to,
277 subject: `[PeerTube] Video ${videoName} blacklisted`,
26b7305a
C
278 text
279 }
280
281 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
282 }
283
f7cc67b4 284 addVideoUnblacklistNotification (to: string[], video: VideoModel) {
cef534ed 285 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
26b7305a
C
286
287 const text = 'Hi,\n\n' +
cef534ed 288 `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
26b7305a
C
289 '\n\n' +
290 'Cheers,\n' +
291 `PeerTube.`
292
26b7305a 293 const emailPayload: EmailPayload = {
cef534ed 294 to,
26b7305a
C
295 subject: `[PeerTube] Video ${video.name} unblacklisted`,
296 text
297 }
298
299 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
300 }
301
cef534ed
C
302 addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) {
303 const text = `Hi dear user,\n\n` +
304 `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` +
305 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
306 `If you are not the person who initiated this request, please ignore this email.\n\n` +
307 `Cheers,\n` +
308 `PeerTube.`
309
310 const emailPayload: EmailPayload = {
311 to: [ to ],
312 subject: 'Reset your PeerTube password',
313 text
314 }
315
316 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
317 }
318
319 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
320 const text = `Welcome to PeerTube,\n\n` +
321 `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` +
322 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
323 `If you are not the person who initiated this request, please ignore this email.\n\n` +
324 `Cheers,\n` +
325 `PeerTube.`
326
327 const emailPayload: EmailPayload = {
328 to: [ to ],
329 subject: 'Verify your PeerTube email',
330 text
331 }
332
333 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
334 }
335
eacb25c4
C
336 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
337 const reasonString = reason ? ` for the following reason: ${reason}` : ''
338 const blockedWord = blocked ? 'blocked' : 'unblocked'
339 const blockedString = `Your account ${user.username} on ${CONFIG.WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
340
341 const text = 'Hi,\n\n' +
342 blockedString +
343 '\n\n' +
344 'Cheers,\n' +
345 `PeerTube.`
346
347 const to = user.email
348 const emailPayload: EmailPayload = {
349 to: [ to ],
350 subject: '[PeerTube] Account ' + blockedWord,
351 text
352 }
353
354 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
355 }
356
ecb4e35f 357 sendMail (to: string[], subject: string, text: string) {
cef534ed 358 if (!this.enabled) {
ecb4e35f
C
359 throw new Error('Cannot send mail because SMTP is not configured.')
360 }
361
362 return this.transporter.sendMail({
363 from: CONFIG.SMTP.FROM_ADDRESS,
364 to: to.join(','),
365 subject,
366 text
367 })
368 }
369
370 private dieOnConnectionFailure (err?: Error) {
d5b7d911 371 logger.error('Failed to connect to SMTP %s:%d.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT, { err })
ecb4e35f
C
372 process.exit(-1)
373 }
374
375 static get Instance () {
376 return this.instance || (this.instance = new this())
377 }
378}
379
380// ---------------------------------------------------------------------------
381
382export {
383 Emailer
384}