aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/emailer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/emailer.ts')
-rw-r--r--server/lib/emailer.ts261
1 files changed, 217 insertions, 44 deletions
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 9327792fb..672414cc0 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -1,5 +1,4 @@
1import { createTransport, Transporter } from 'nodemailer' 1import { createTransport, Transporter } from 'nodemailer'
2import { UserRight } from '../../shared/models/users'
3import { isTestInstance } from '../helpers/core-utils' 2import { isTestInstance } from '../helpers/core-utils'
4import { bunyanLogger, logger } from '../helpers/logger' 3import { bunyanLogger, logger } from '../helpers/logger'
5import { CONFIG } from '../initializers' 4import { CONFIG } from '../initializers'
@@ -8,6 +7,11 @@ import { VideoModel } from '../models/video/video'
8import { JobQueue } from './job-queue' 7import { JobQueue } from './job-queue'
9import { EmailPayload } from './job-queue/handlers/email' 8import { EmailPayload } from './job-queue/handlers/email'
10import { readFileSync } from 'fs-extra' 9import { readFileSync } from 'fs-extra'
10import { VideoCommentModel } from '../models/video/video-comment'
11import { VideoAbuseModel } from '../models/video/video-abuse'
12import { VideoBlacklistModel } from '../models/video/video-blacklist'
13import { VideoImportModel } from '../models/video/video-import'
14import { ActorFollowModel } from '../models/activitypub/actor-follow'
11 15
12class Emailer { 16class Emailer {
13 17
@@ -22,7 +26,7 @@ class Emailer {
22 if (this.initialized === true) return 26 if (this.initialized === true) return
23 this.initialized = true 27 this.initialized = true
24 28
25 if (CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) { 29 if (Emailer.isEnabled()) {
26 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) 30 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
27 31
28 let tls 32 let tls
@@ -57,6 +61,10 @@ class Emailer {
57 } 61 }
58 } 62 }
59 63
64 static isEnabled () {
65 return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
66 }
67
60 async checkConnectionOrDie () { 68 async checkConnectionOrDie () {
61 if (!this.transporter) return 69 if (!this.transporter) return
62 70
@@ -72,50 +80,158 @@ class Emailer {
72 } 80 }
73 } 81 }
74 82
75 addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) { 83 addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
84 const channelName = video.VideoChannel.getDisplayName()
85 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
86
76 const text = `Hi dear user,\n\n` + 87 const text = `Hi dear user,\n\n` +
77 `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` + 88 `Your subscription ${channelName} just published a new video: ${video.name}` +
78 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + 89 `\n\n` +
79 `If you are not the person who initiated this request, please ignore this email.\n\n` + 90 `You can view it on ${videoUrl} ` +
91 `\n\n` +
80 `Cheers,\n` + 92 `Cheers,\n` +
81 `PeerTube.` 93 `PeerTube.`
82 94
83 const emailPayload: EmailPayload = { 95 const emailPayload: EmailPayload = {
84 to: [ to ], 96 to,
85 subject: 'Reset your PeerTube password', 97 subject: channelName + ' just published a new video',
86 text 98 text
87 } 99 }
88 100
89 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 101 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
90 } 102 }
91 103
92 addVerifyEmailJob (to: string, verifyEmailUrl: string) { 104 addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') {
93 const text = `Welcome to PeerTube,\n\n` + 105 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
94 `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` + 106 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
95 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + 107
96 `If you are not the person who initiated this request, please ignore this email.\n\n` + 108 const text = `Hi dear user,\n\n` +
109 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
110 `\n\n` +
97 `Cheers,\n` + 111 `Cheers,\n` +
98 `PeerTube.` 112 `PeerTube.`
99 113
100 const emailPayload: EmailPayload = { 114 const emailPayload: EmailPayload = {
101 to: [ to ], 115 to,
102 subject: 'Verify your PeerTube email', 116 subject: 'New follower on your channel ' + followingName,
117 text
118 }
119
120 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
121 }
122
123 myVideoPublishedNotification (to: string[], video: VideoModel) {
124 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
125
126 const text = `Hi dear user,\n\n` +
127 `Your video ${video.name} has been published.` +
128 `\n\n` +
129 `You can view it on ${videoUrl} ` +
130 `\n\n` +
131 `Cheers,\n` +
132 `PeerTube.`
133
134 const emailPayload: EmailPayload = {
135 to,
136 subject: `Your video ${video.name} is published`,
137 text
138 }
139
140 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
141 }
142
143 myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
144 const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
145
146 const text = `Hi dear user,\n\n` +
147 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
148 `\n\n` +
149 `You can view the imported video on ${videoUrl} ` +
150 `\n\n` +
151 `Cheers,\n` +
152 `PeerTube.`
153
154 const emailPayload: EmailPayload = {
155 to,
156 subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
157 text
158 }
159
160 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
161 }
162
163 myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
164 const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
165
166 const text = `Hi dear user,\n\n` +
167 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
168 `\n\n` +
169 `See your videos import dashboard for more information: ${importUrl}` +
170 `\n\n` +
171 `Cheers,\n` +
172 `PeerTube.`
173
174 const emailPayload: EmailPayload = {
175 to,
176 subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
177 text
178 }
179
180 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
181 }
182
183 addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
184 const accountName = comment.Account.getDisplayName()
185 const video = comment.Video
186 const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
187
188 const text = `Hi dear user,\n\n` +
189 `A new comment has been posted by ${accountName} on your video ${video.name}` +
190 `\n\n` +
191 `You can view it on ${commentUrl} ` +
192 `\n\n` +
193 `Cheers,\n` +
194 `PeerTube.`
195
196 const emailPayload: EmailPayload = {
197 to,
198 subject: 'New comment on your video ' + video.name,
103 text 199 text
104 } 200 }
105 201
106 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 202 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
107 } 203 }
108 204
109 async addVideoAbuseReportJob (videoId: number) { 205 addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) {
110 const video = await VideoModel.load(videoId) 206 const accountName = comment.Account.getDisplayName()
111 if (!video) throw new Error('Unknown Video id during Abuse report.') 207 const video = comment.Video
208 const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
209
210 const text = `Hi dear user,\n\n` +
211 `${accountName} mentioned you on video ${video.name}` +
212 `\n\n` +
213 `You can view the comment on ${commentUrl} ` +
214 `\n\n` +
215 `Cheers,\n` +
216 `PeerTube.`
217
218 const emailPayload: EmailPayload = {
219 to,
220 subject: 'Mention on video ' + video.name,
221 text
222 }
223
224 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
225 }
226
227 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
228 const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
112 229
113 const text = `Hi,\n\n` + 230 const text = `Hi,\n\n` +
114 `Your instance received an abuse for the following video ${video.url}\n\n` + 231 `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
115 `Cheers,\n` + 232 `Cheers,\n` +
116 `PeerTube.` 233 `PeerTube.`
117 234
118 const to = await UserModel.listEmailsWithRight(UserRight.MANAGE_VIDEO_ABUSES)
119 const emailPayload: EmailPayload = { 235 const emailPayload: EmailPayload = {
120 to, 236 to,
121 subject: '[PeerTube] Received a video abuse', 237 subject: '[PeerTube] Received a video abuse',
@@ -125,16 +241,27 @@ class Emailer {
125 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 241 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
126 } 242 }
127 243
128 async addVideoBlacklistReportJob (videoId: number, reason?: string) { 244 addNewUserRegistrationNotification (to: string[], user: UserModel) {
129 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) 245 const text = `Hi,\n\n` +
130 if (!video) throw new Error('Unknown Video id during Blacklist report.') 246 `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` +
131 // It's not our user 247 `Cheers,\n` +
132 if (video.remote === true) return 248 `PeerTube.`
249
250 const emailPayload: EmailPayload = {
251 to,
252 subject: '[PeerTube] New user registration on ' + CONFIG.WEBSERVER.HOST,
253 text
254 }
255
256 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
257 }
133 258
134 const user = await UserModel.loadById(video.VideoChannel.Account.userId) 259 addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
260 const videoName = videoBlacklist.Video.name
261 const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
135 262
136 const reasonString = reason ? ` for the following reason: ${reason}` : '' 263 const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
137 const blockedString = `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.` 264 const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
138 265
139 const text = 'Hi,\n\n' + 266 const text = 'Hi,\n\n' +
140 blockedString + 267 blockedString +
@@ -142,33 +269,26 @@ class Emailer {
142 'Cheers,\n' + 269 'Cheers,\n' +
143 `PeerTube.` 270 `PeerTube.`
144 271
145 const to = user.email
146 const emailPayload: EmailPayload = { 272 const emailPayload: EmailPayload = {
147 to: [ to ], 273 to,
148 subject: `[PeerTube] Video ${video.name} blacklisted`, 274 subject: `[PeerTube] Video ${videoName} blacklisted`,
149 text 275 text
150 } 276 }
151 277
152 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 278 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
153 } 279 }
154 280
155 async addVideoUnblacklistReportJob (videoId: number) { 281 addVideoUnblacklistNotification (to: string[], video: VideoModel) {
156 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) 282 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
157 if (!video) throw new Error('Unknown Video id during Blacklist report.')
158 // It's not our user
159 if (video.remote === true) return
160
161 const user = await UserModel.loadById(video.VideoChannel.Account.userId)
162 283
163 const text = 'Hi,\n\n' + 284 const text = 'Hi,\n\n' +
164 `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` + 285 `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
165 '\n\n' + 286 '\n\n' +
166 'Cheers,\n' + 287 'Cheers,\n' +
167 `PeerTube.` 288 `PeerTube.`
168 289
169 const to = user.email
170 const emailPayload: EmailPayload = { 290 const emailPayload: EmailPayload = {
171 to: [ to ], 291 to,
172 subject: `[PeerTube] Video ${video.name} unblacklisted`, 292 subject: `[PeerTube] Video ${video.name} unblacklisted`,
173 text 293 text
174 } 294 }
@@ -176,6 +296,40 @@ class Emailer {
176 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 296 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
177 } 297 }
178 298
299 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
300 const text = `Hi dear user,\n\n` +
301 `A reset password procedure for your account ${to} has been requested on ${CONFIG.WEBSERVER.HOST} ` +
302 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
303 `If you are not the person who initiated this request, please ignore this email.\n\n` +
304 `Cheers,\n` +
305 `PeerTube.`
306
307 const emailPayload: EmailPayload = {
308 to: [ to ],
309 subject: 'Reset your PeerTube password',
310 text
311 }
312
313 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
314 }
315
316 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
317 const text = `Welcome to PeerTube,\n\n` +
318 `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` +
319 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
320 `If you are not the person who initiated this request, please ignore this email.\n\n` +
321 `Cheers,\n` +
322 `PeerTube.`
323
324 const emailPayload: EmailPayload = {
325 to: [ to ],
326 subject: 'Verify your PeerTube email',
327 text
328 }
329
330 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
331 }
332
179 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { 333 addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
180 const reasonString = reason ? ` for the following reason: ${reason}` : '' 334 const reasonString = reason ? ` for the following reason: ${reason}` : ''
181 const blockedWord = blocked ? 'blocked' : 'unblocked' 335 const blockedWord = blocked ? 'blocked' : 'unblocked'
@@ -197,13 +351,32 @@ class Emailer {
197 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) 351 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
198 } 352 }
199 353
200 sendMail (to: string[], subject: string, text: string) { 354 addContactFormJob (fromEmail: string, fromName: string, body: string) {
201 if (!this.transporter) { 355 const text = 'Hello dear admin,\n\n' +
356 fromName + ' sent you a message' +
357 '\n\n---------------------------------------\n\n' +
358 body +
359 '\n\n---------------------------------------\n\n' +
360 'Cheers,\n' +
361 'PeerTube.'
362
363 const emailPayload: EmailPayload = {
364 from: fromEmail,
365 to: [ CONFIG.ADMIN.EMAIL ],
366 subject: '[PeerTube] Contact form submitted',
367 text
368 }
369
370 return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
371 }
372
373 sendMail (to: string[], subject: string, text: string, from?: string) {
374 if (!Emailer.isEnabled()) {
202 throw new Error('Cannot send mail because SMTP is not configured.') 375 throw new Error('Cannot send mail because SMTP is not configured.')
203 } 376 }
204 377
205 return this.transporter.sendMail({ 378 return this.transporter.sendMail({
206 from: CONFIG.SMTP.FROM_ADDRESS, 379 from: from || CONFIG.SMTP.FROM_ADDRESS,
207 to: to.join(','), 380 to: to.join(','),
208 subject, 381 subject,
209 text 382 text