Skip to content

Commit

Permalink
Blackhole bounced emails
Browse files Browse the repository at this point in the history
  • Loading branch information
bcomnes committed Nov 17, 2022
1 parent 43ca3d3 commit 63ed7ab
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 69 deletions.
25 changes: 19 additions & 6 deletions routes/api/register/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export default async function registerRoutes (fastify, opts) {
const results = await client.query(query)
const { email_verify_token, ...user } = results.rows[0]

await client.query('commit')

const token = await reply.createJWTToken(user)
reply.setJWTCookie(token)

Expand All @@ -78,12 +80,23 @@ export default async function registerRoutes (fastify, opts) {
fastify.metrics.userCreatedCounter.inc()

fastify.pqueue.add(async () => {
await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: email,
subject: 'Verify your account email address', // Subject line
text: verifyEmailBody({ email: user.email, username: user.username, host: fastify.config.HOST, token: email_verify_token })
})
const blackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${email}
fetch first row only;
`)

if (blackholeResults.rows.length === 0 || blackholeResults.rows[0].disabled === false) {
await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: email,
subject: 'Verify your account email address', // Subject line
text: verifyEmailBody({ email: user.email, username: user.username, host: fastify.config.HOST, token: email_verify_token })
})
} else {
fastify.log.warn({ email }, 'Skipping email for blocked email address')
}
})

return {
Expand Down
40 changes: 36 additions & 4 deletions routes/api/sns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,47 @@ export default async function snsRoutes (fastify, opts) {
} else if (data.Type === 'Notification') {
switch (data.Message?.notificationType) {
case 'Bounce': {
fastify.log.info('Bounce')
fastify.log.info({
...data.Message?.bounce
}, 'Bounced email')

if (data.Message?.bounce?.bounceType === 'Permanent') {
fastify.log.warn({ bouncedRecipients: data.Message?.bounce?.bouncedRecipients }, 'Black-hole perminent bounce')
return fastify.pg.transact(async client => {
const emailsToBlock = data.Message?.bounce?.bouncedRecipients.map(r => r.emailAddress)

const blockEmailQuery = SQL`
insert into email_blackhole (email, bounce_count, disabled)
values ${SQL.glue(emailsToBlock.map(email => SQL`(${email},${1},${true})`), ' , ')}
on conflict (email)
do update
set bounce_count = email_blackhole.bounce_count + excluded.bounce_count, disabled = true;
`

await client.query(blockEmailQuery)
})
}
break
}
case 'Complaint': {
fastify.log.info('Complaint')
break
fastify.log.warn({ ...data.Message?.complaint }, 'Complaint')
fastify.log.warn({ bouncedRecipients: data.Message?.complaint?.complainedRecipients }, 'Black-hole complaint')
return fastify.pg.transact(async client => {
const emailsToBlock = data.Message?.complaint?.complainedRecipients.map(r => r.emailAddress)

const blockEmailQuery = SQL`
insert into email_blackhole (email, bounce_count, disabled)
values ${SQL.glue(emailsToBlock.map(email => SQL`(${email},${1},${true})`), ' , ')}
on conflict (email)
do update
set bounce_count = email_blackhole.bounce_count + excluded.bounce_count, disabled = true;
`

await client.query(blockEmailQuery)
})
}
case 'Delivery': {
fastify.log.info('Delivery')
fastify.log.info({ ...data.Message?.delivery }, 'Delivery')
break
}
default: {
Expand Down
39 changes: 25 additions & 14 deletions routes/api/user/email/confirm-email.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,32 @@ export async function confirmEmail (fastify, opts) {
const updatedUser = queryResults.rows.pop()

fastify.pqueue.add(async () => {
return await Promise.all([
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: updatedUser.email,
subject: 'Verify your email address', // Subject line
text: verifyEmailBody({
username: updatedUser.username,
transport: fastify.config.TRANSPORT,
host: fastify.config.HOST,
token: updatedUser.email_verify_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
const blackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${updatedUser.email}
fetch first row only;
`)

if (blackholeResults.rows.length === 0 || blackholeResults.rows[0].disabled === false) {
return await Promise.all([
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: updatedUser.email,
subject: 'Verify your email address', // Subject line
text: verifyEmailBody({
username: updatedUser.username,
transport: fastify.config.TRANSPORT,
host: fastify.config.HOST,
token: updatedUser.email_verify_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
})
})
})
])
])
} else {
fastify.log.warn({ email: updatedUser.email }, 'Skipping email for blocked email address')
}
})

reply.code(202)
Expand Down
77 changes: 53 additions & 24 deletions routes/api/user/email/post-email.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,32 +69,61 @@ export async function postEmail (fastify, opts) {
const updatedUser = queryResults.rows.pop()

fastify.pqueue.add(async () => {
return await Promise.all([
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: email,
subject: 'Verify your updated email address', // Subject line
text: verifyEmailUpdateBody({
username: updatedUser.username,
host: fastify.config.HOST,
token: updatedUser.pending_email_update_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
const emailJobs = []

const verifyBlackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${updatedUser.pending_email_update}
fetch first row only;
`)

if (verifyBlackholeResults.rows.length === 0 || verifyBlackholeResults.rows[0].disabled === false) {
emailJobs.push(
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: updatedUser.pending_email_update,
subject: 'Verify your updated email address', // Subject line
text: verifyEmailUpdateBody({
username: updatedUser.username,
host: fastify.config.HOST,
token: updatedUser.pending_email_update_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
})
})
}),
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: email,
subject: 'Your email address was changed', // Subject line
text: notifyOldEmailBody({
username: updatedUser.username,
host: fastify.config.HOST,
token: updatedUser.pending_email_update_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
)
} else {
fastify.log.warn({ email: updatedUser.pending_email_update }, 'Skipping email for blocked email address')
}

const notifyBlackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${updatedUser.email}
fetch first row only;
`)

if (notifyBlackholeResults.rows.length === 0 || notifyBlackholeResults.rows[0].disabled === false) {
emailJobs.push(
fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: updatedUser.email,
subject: 'Verify your updated email address', // Subject line
text: notifyOldEmailBody({
username: updatedUser.username,
host: fastify.config.HOST,
token: updatedUser.pending_email_update_token,
oldEmail: updatedUser.email,
newEmail: updatedUser.pending_email_update
})
})
})
])
)
} else {
fastify.log.warn({ email: updatedUser.email }, 'Skipping email for blocked email address')
}

return await Promise.all(emailJobs)
})

reply.code(202)
Expand Down
26 changes: 18 additions & 8 deletions routes/api/user/password/post-password.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,25 @@ export async function postPassword (fastify, opts) {
await client.query(updateQuery)

fastify.pqueue.add(async () => {
return await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: user.email,
subject: 'Your password has been updated', // Subject line
text: passwordUpdatedBody({
username: user.username,
host: fastify.config.HOST
const blackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${user.email}
fetch first row only;
`)
if (blackholeResults.rows.length === 0 || blackholeResults.rows[0].disabled === false) {
return await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: user.email,
subject: 'Your password has been updated', // Subject line
text: passwordUpdatedBody({
username: user.username,
host: fastify.config.HOST
})
})
})
} else {
fastify.log.warn({ email: user.email }, 'Skipping email for blocked email address')
}
})

reply.code(202)
Expand Down
33 changes: 22 additions & 11 deletions routes/api/user/password/reset-password.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,29 @@ export async function resetPassword (fastify, opts) {
const { password_reset_token } = resetTokenResults.rows.pop()

fastify.pqueue.add(async () => {
return await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: user.email,
subject: 'Your password has been updated', // Subject line
text: passwordResetBody({
token: password_reset_token,
userID: user.id,
username: user.username,
host: fastify.config.HOST,
transport: fastify.config.TRANSPORT
const blackholeResults = await fastify.pg.query(SQL`
select email, bounce_count, disabled
from email_blackhole
where email = ${user.email}
fetch first row only;
`)

if (blackholeResults.rows.length === 0 || blackholeResults.rows[0].disabled === false) {
return await fastify.email.sendMail({
from: `"Breadcrum.net 🥖" <${fastify.config.APP_EMAIL}>`,
to: user.email,
subject: 'Your password has been updated', // Subject line
text: passwordResetBody({
token: password_reset_token,
userID: user.id,
username: user.username,
host: fastify.config.HOST,
transport: fastify.config.TRANSPORT
})
})
})
} else {
fastify.log.warn({ email: user.email }, 'Skipping email for blocked email address')
}
})

reply.code(202)
Expand Down
3 changes: 1 addition & 2 deletions web/register/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export const page = Component(() => {

useEffect(() => {
if ((user && !loading)) {
const redirectTarget = `${window.location.pathname}${window.location.search}`
window.location.replace(`/login?redirect=${encodeURIComponent(redirectTarget)}`)
window.location.replace('/bookmarks')
}
}, [user])

Expand Down

0 comments on commit 63ed7ab

Please sign in to comment.