Send email on RSVP

This commit is contained in:
2017-02-26 14:51:22 +00:00
parent 274d5a3629
commit d2c57c5ef0
5 changed files with 71 additions and 28 deletions

View File

@@ -25,6 +25,7 @@
"isomorphic-style-loader": "^1.1.0", "isomorphic-style-loader": "^1.1.0",
"jsonwebtoken": "^7.2.1", "jsonwebtoken": "^7.2.1",
"node-fetch": "^1.6.3", "node-fetch": "^1.6.3",
"nodemailer": "^3.1.3",
"normalize.css": "^5.0.0", "normalize.css": "^5.0.0",
"passport": "^0.3.2", "passport": "^0.3.2",
"passport-facebook": "^2.1.1", "passport-facebook": "^2.1.1",

62
src/core/emailer.js Normal file
View File

@@ -0,0 +1,62 @@
import Nodemailer from 'nodemailer';
import { email, starters, mains, getRsvpEnd } from '../config';
import { handleError } from '../core/rollbar';
const transporter = Nodemailer.createTransport({
service: 'gmail',
auth: {
user: email.senderAddress,
pass: email.password,
},
});
// Debounce sending the email to avoid spamming
const emailTimers = {};
function getEmailText(person) {
return `Dear ${person.firstname}.\n\n
Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.\n
${person.attending ? `Your meal choices are ${starters[person.starter]} and ${mains[person.main]}.\n` : ''}
${person.attending && person.dietary.length > 0 ? `Your dietary requirements are: ${person.dietary}.\n` : ''}
You can modify your RSVP up until ${getRsvpEnd()} here: https://danielandhana.com/${person.password}.\n
${person.attending ? 'See you at our wedding,' : 'See you soon,'}\n
Daniel and Hana
`;
}
function getEmailHTML(person) {
return `<p>Dear ${person.firstname}.</p>
<p>Thanks for your RSVP, ${person.attending ? 'we\'re delighted to hear you will be attending' : 'we\'re sorry to hear you will not be attending'}.</p>
${person.attending ? `<p>Your meal choices are <b>${starters[person.starter]}</b> and <b>${mains[person.main]}</b>.</p>` : ''}
${person.attending && person.dietary.length > 0 ? `<p>Your dietary requirements are: <b>${person.dietary}</b>.</p>` : ''}
<p>You can modify your RSVP up until <b>${getRsvpEnd()}</b> <a href="https://danielandhana.com/${person.password}">here</a>.</p>
<p>${person.attending ? 'See you at our wedding,' : 'See you soon,'}</p>
<p>Daniel and Hana</p>
`;
}
export const sendRSVPEmail = function sendRSVPEmail(person) {
const mailOptions = {
from: `"${email.senderName}" <${email.senderAddress}>`,
to: person.email,
subject: 'Thank you for your RSVP',
text: getEmailText(person),
html: getEmailHTML(person),
};
// send mail with defined transport object
transporter.sendMail(mailOptions, (error) => {
if (error) {
handleError(error);
}
});
};
export const sendRSVPEmailWithDebounce = function sendRSVPEmailWithDebounce(person) {
if (person.key) {
// Stop previous email
clearTimeout(emailTimers[person.key]);
emailTimers[person.key] = setTimeout(() => {
sendRSVPEmail(person);
}, 10000);
}
};

View File

@@ -3,40 +3,14 @@ import Layout from '../../components/Layout';
import fetchQL from '../../core/fetchQL'; import fetchQL from '../../core/fetchQL';
import Rsvp from './Rsvp'; import Rsvp from './Rsvp';
import NotFound from '../notFound/NotFound'; import NotFound from '../notFound/NotFound';
import { rsvpEndDate } from '../../config'; import { rsvpEndDate, getRsvpEnd } from '../../config';
let title = 'RSVP'; let title = 'RSVP';
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
export default { export default {
path: '/rsvp/:password', path: '/rsvp/:password',
getRsvpEnd() {
const date = rsvpEndDate.getDate();
let dateSuffix = 'th';
if (date === 1 || date === 21 || date === 31) { dateSuffix = 'st'; }
if (date === 2 || date === 22) { dateSuffix = 'nd'; }
if (date === 3 || date === 23) { dateSuffix = 'rd'; }
const month = months[rsvpEndDate.getMonth()];
const year = rsvpEndDate.getFullYear();
return `${date}${dateSuffix} ${month} ${year}`;
},
isEditable() { isEditable() {
return new Date() < rsvpEndDate; return new Date() < rsvpEndDate;
}, },
@@ -68,7 +42,7 @@ export default {
<Rsvp <Rsvp
title={title} title={title}
people={people} people={people}
rsvpEnd={this.getRsvpEnd()} rsvpEnd={getRsvpEnd()}
isEditable={this.isEditable()} isEditable={this.isEditable()}
/> />
</Layout> </Layout>

View File

@@ -26,6 +26,7 @@ import Person from './data/models/Person';
import Rollbar from './core/rollbar'; import Rollbar from './core/rollbar';
import { sendSlackMsg, sendSlackMsgWithDebounce } from './core/slack'; import { sendSlackMsg, sendSlackMsgWithDebounce } from './core/slack';
import sequelize from './data/sequelize'; import sequelize from './data/sequelize';
import { sendRSVPEmailWithDebounce } from './core/emailer';
const app = express(); const app = express();
@@ -170,6 +171,7 @@ app.post('/rsvp/save', (req, res) => {
const dietary = (data.attending && data.dietary) ? `\nDietary requirements: ${data.dietary}` : ''; const dietary = (data.attending && data.dietary) ? `\nDietary requirements: ${data.dietary}` : '';
sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key); sendSlackMsgWithDebounce(`${data.firstname} ${data.lastname} has saved their RSVP:${attending}${starter}${main}${dietary}`, '#wedding-rsvps', req.body.key);
sendRSVPEmailWithDebounce(data);
} }
res.json({ res.json({
success: true, success: true,

View File

@@ -4423,6 +4423,10 @@ node-uuid@1.4.x, node-uuid@~1.4.4, node-uuid@~1.4.7:
version "1.4.7" version "1.4.7"
resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f"
nodemailer@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-3.1.3.tgz#331b3265522f52b128bfc2dcaba9fe317b82f018"
"nopt@2 || 3", nopt@3.0.x, nopt@~3.0.6: "nopt@2 || 3", nopt@3.0.x, nopt@~3.0.6:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"