Skip to content

Commit

Permalink
add unsubscribe support (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
bhavanau committed Jan 2, 2021
1 parent ab4eb83 commit 4556f91
Show file tree
Hide file tree
Showing 15 changed files with 182 additions and 93 deletions.
17 changes: 0 additions & 17 deletions example/.github/workflows

This file was deleted.

22 changes: 0 additions & 22 deletions example/app.js

This file was deleted.

39 changes: 0 additions & 39 deletions example/candymail.automation.json

This file was deleted.

39 changes: 39 additions & 0 deletions examples/candymail.automation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"automations": [
{
"name": "automation1",
"description": "tell user about pro features",
"trigger_name": "proplan",
"emails": [
{
"trigger": "time",
"sendDelay": 1,
"subject": "w1e1",
"body": "Customizations are great",
"from": "[email protected]"
},
{
"trigger": "time",
"sendDelay": 3,
"subject": "w1e2",
"body": "Customizations are great",
"from": "[email protected]"
}
]
},
{
"name": "automation2",
"description": "tell user about pro features 2",
"trigger_name": "proplan",
"emails": [
{
"trigger": "time",
"sendDelay": 2,
"subject": "w2e1",
"body": "Customizations are great",
"from": "[email protected]"
}
]
}
]
}
40 changes: 40 additions & 0 deletions examples/running-with-express-server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require('dotenv').config()
const path = require('path')
const candymail = require('../../index')
const express = require('express')
const app = express()
const port = 3000

const automationPath = path.resolve('examples', 'candymail.automation.json')
candymail.init(automationPath, {
senderEmail: process.env.MAIL_USER,
senderPassword: process.env.MAIL_PASSWORD,
hostingURL: 'http://localhost:3000'
})

candymail.start()

const someConditionSatisfiedByUser = () => {
const user = '[email protected]'
candymail.runAutomation('automation1', user)
}

app.get('/', (req, res) => {
res.send('Welcome to Candymail Demo. Go to /trigger to trigger the `automation1` email automation. Be sure to replace email with yours in the `someConditionSatisfiedByUser` method to be able to view the messages.')
})

app.get('/trigger', (req, res) => {
someConditionSatisfiedByUser()
res.send(candymail.getAllScheduledMessages())
})

app.get('/unsubscribe', (req, res) => {
const { email } = req.query
console.log(email)
candymail.unsubscribeUser(email)
res.send(`Sent a unsubscribe request for ${email}`)
})

app.listen(port, () => {
console.log(`Learn about our new features at http://localhost:${port}/trigger`)
})
16 changes: 16 additions & 0 deletions examples/running-with-express-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "candymail-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index"
},
"author": "",
"license": "ISC",
"dependencies": {
"candymail": "^1.0.5",
"dotenv": "^8.2.0",
"express": "^4.17.1"
}
}
22 changes: 22 additions & 0 deletions examples/simple/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const path = require('path')
const candymail = require('../../index')

const automationPath = path.resolve('examples', 'candymail.automation.json')

candymail.init(automationPath, {
senderEmail: process.env.MAIL_USER,
senderPassword: process.env.MAIL_PASSWORD,
hostingURL: 'http://localhost:3000'
})

candymail.start()

// candymail.unsubscribeUser('[email protected]') // Immediatedly unsubscribe user and they will not receive any more messages

const someConditionSatisfiedByUser = () => {
const user = '[email protected]'
candymail.runAutomation('automation1', user)
}

someConditionSatisfiedByUser()
console.log(candymail.getAllScheduledMessages())
10 changes: 6 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ require('dotenv').config()
const cron = require('node-cron')

const { generateDateKey, sendEmail } = require('./src/helper')
const { init, runAutomation, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages } = require('./src/scheduler')
const { init, runAutomation, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages, unsubscribeUser } = require('./src/scheduler')

// scheduler runs automatically on import
const task = cron.schedule('0 * * * *', () => {
console.log(`Running cron work at ${(new Date()).getHours()}`)
// TODO: get data in ETC all the time, this is local time to the machine
console.log(`Current queue is ${JSON.stringify(getAllScheduledMessages())}`)
sendMessagesNow()
}, {
scheduled: false
})

const start = () => {
task.start()
console.log('start', task.getStatus(), new Date().toLocaleTimeString())
console.log(`Timer status: ${task.getStatus()}`)
}

const stop = () => {
Expand All @@ -30,14 +31,15 @@ const destroy = () => {

const sendMessagesNow = () => {
const dateKey = generateDateKey()
console.log(`Date right now is ${dateKey}`)
const messagesForThisHour = getScheduledMessagesAtTime(dateKey)

if (messagesForThisHour) {
messagesForThisHour.forEach(message => {
console.log('sendEmail')
console.log('sendEmail', message, sendEmail)
sendEmail(message)
})
} else { console.log('no messages to send at this time') }
}

module.exports = { init, start, stop, destroy, runAutomation, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages, sendMessagesNow }
module.exports = { init, start, stop, destroy, runAutomation, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages, sendMessagesNow, unsubscribeUser }
2 changes: 1 addition & 1 deletion src/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
let config = {
senderEmail: '',
senderPassword: '',
automationPath: ''
hostingURL: ''
}

const setConfig = (userConfig) => {
Expand Down
15 changes: 12 additions & 3 deletions src/helper.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@

const mailer = require('nodemailer')
const { getConfig } = require('./config.js')
const { hasUnsubscribed } = require('./messages')

const sendEmail = ({ template, sendFrom, sendTo, subject, body }) => {
console.log('sending email', sendTo, 'is here and it is ', hasUnsubscribed(sendTo))
if (hasUnsubscribed(sendTo)) {
throw new Error(`The user ${sendTo} you are trying to send a message to has already unsubscribed`)
}

const transporter = mailer.createTransport({
service: 'gmail',
auth: {
user: getConfig().senderEmail,
pass: getConfig().senderPassword
user: getConfig().senderEmail || process.env.MAIL_USER,
pass: getConfig().senderPassword || process.env.MAIL_PASSWORD
}
})

console.log('hosting is on ', getConfig().hostingURL)
const html = `<p>${body}</p><br><a href="${getConfig().hostingURL}/unsubscribe?email=${sendTo}">Click here to unsubscribe</a>`

const mailOptions = {
from: sendFrom,
to: sendTo,
subject,
html: body
html
}
transporter.sendMail(mailOptions, function (err, info) {
if (err) {
Expand Down
12 changes: 11 additions & 1 deletion src/messages.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
let scheduledMessages = {}
const unsubscribedUsers = []

const addScheduledMessage = (time, messageOptions) => {
if (time in scheduledMessages) {
Expand All @@ -8,6 +9,15 @@ const addScheduledMessage = (time, messageOptions) => {
}
}

const unsubscribeUser = (email) => {
unsubscribedUsers.push(email)
console.log(unsubscribedUsers)
}

const hasUnsubscribed = (email) => {
return unsubscribedUsers.includes(email)
}

const getScheduledMessagesAtTime = (time) => {
return scheduledMessages[time]
}
Expand All @@ -20,4 +30,4 @@ const clearAllScheduledMessages = () => {
scheduledMessages = {}
}

module.exports = { addScheduledMessage, getScheduledMessagesAtTime, getAllScheduledMessages, clearAllScheduledMessages }
module.exports = { addScheduledMessage, getScheduledMessagesAtTime, getAllScheduledMessages, clearAllScheduledMessages, unsubscribeUser, hasUnsubscribed }
4 changes: 2 additions & 2 deletions src/scheduler.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const path = require('path')
const { setConfig } = require('./config')
const { generateDateKey } = require('./helper')
const { addScheduledMessage, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages } = require('./messages')
const { addScheduledMessage, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages, unsubscribeUser, hasUnsubscribed } = require('./messages') // TODO Clean these propagating imports

let loadedAutomations = {}

Expand Down Expand Up @@ -38,4 +38,4 @@ const runAutomation = (automation, sendTo) => {
build(messagesInAutomation.emails, sendTo)
}

module.exports = { init, runAutomation, addScheduledMessage, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages }
module.exports = { init, runAutomation, addScheduledMessage, getAllScheduledMessages, getScheduledMessagesAtTime, clearAllScheduledMessages, unsubscribeUser, hasUnsubscribed }
7 changes: 7 additions & 0 deletions src/unsubscribeTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require('dotenv').config()
const { sendEmail } = require('./helper')
const { unsubscribeUser } = require('./messages')

// unsubscribeUser('[email protected]')

sendEmail({ template: 'template', sendFrom: '[email protected]', sendTo: '[email protected]', subject: 'subject', body: '<h1> Welcome</h1> <p>That was easy!</p><br><a href="/unsubscribe">Click here to unsubscribe</a>' })
9 changes: 5 additions & 4 deletions tests/scheduler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ jest.mock('../src/helper', () => {
})

describe('Basic Tests', () => {
beforeEach(() => {
afterEach(() => {
jest.clearAllMocks()
jest.resetAllMocks()
// jest.resetAllMocks()
scheduler.clearAllScheduledMessages()
})

test('should send email at time', () => {
Date.now = jest.fn(() => new Date('2020-08-20T03:20:30Z'))
scheduler.addScheduledMessage('8/19/2020:23', { template: 'template', sendFrom: 'sendFrom', sendTo: 'sendTo', subject: 'subject', body: 'body' })

console.log(mockHelper.sendEmail)
sendMessagesNow()

expect(mockHelper.sendEmail).toHaveBeenCalledTimes(1)
})

test('should send email at time 1', () => {
test('should correctly send messages with a delay', () => {
scheduler.addScheduledMessage('8/19/2020:23', { template: 'template', sendFrom: 'sendFrom', sendTo: 'sendTo', subject: 'subject', body: 'body' })
scheduler.addScheduledMessage('8/20/2020:1', { template: 'template1', sendFrom: 'sendFrom1', sendTo: 'sendTo1', subject: 'subject1', body: 'body1' })

Expand Down
21 changes: 21 additions & 0 deletions tests/unsubscribe.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { sendMessagesNow } = require('../index')
const scheduler = require('../src/scheduler')

describe('Basic Tests', () => {
afterEach(() => {
jest.clearAllMocks()
jest.resetAllMocks()
scheduler.clearAllScheduledMessages()
})
test('should throw an error when email about to be sent to unsubscribed user', () => {
Date.now = jest.fn(() => new Date('2020-08-20T03:20:30Z'))
const user = '[email protected]'
scheduler.addScheduledMessage('8/19/2020:23', { template: 'template', sendFrom: 'sendFrom', sendTo: user, subject: 'subject', body: 'body' })

scheduler.unsubscribeUser(user)

expect(() => {
sendMessagesNow()
}).toThrow(new Error('The user [email protected] you are trying to send a message to has already unsubscribed'))
})
})

0 comments on commit 4556f91

Please sign in to comment.