Send email on RSVP
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"isomorphic-style-loader": "^1.1.0",
|
||||
"jsonwebtoken": "^7.2.1",
|
||||
"node-fetch": "^1.6.3",
|
||||
"nodemailer": "^3.1.3",
|
||||
"normalize.css": "^5.0.0",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.1.1",
|
||||
|
||||
62
src/core/emailer.js
Normal file
62
src/core/emailer.js
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -3,40 +3,14 @@ import Layout from '../../components/Layout';
|
||||
import fetchQL from '../../core/fetchQL';
|
||||
import Rsvp from './Rsvp';
|
||||
import NotFound from '../notFound/NotFound';
|
||||
import { rsvpEndDate } from '../../config';
|
||||
import { rsvpEndDate, getRsvpEnd } from '../../config';
|
||||
|
||||
let title = 'RSVP';
|
||||
|
||||
const months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
export default {
|
||||
|
||||
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() {
|
||||
return new Date() < rsvpEndDate;
|
||||
},
|
||||
@@ -68,7 +42,7 @@ export default {
|
||||
<Rsvp
|
||||
title={title}
|
||||
people={people}
|
||||
rsvpEnd={this.getRsvpEnd()}
|
||||
rsvpEnd={getRsvpEnd()}
|
||||
isEditable={this.isEditable()}
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
@@ -26,6 +26,7 @@ import Person from './data/models/Person';
|
||||
import Rollbar from './core/rollbar';
|
||||
import { sendSlackMsg, sendSlackMsgWithDebounce } from './core/slack';
|
||||
import sequelize from './data/sequelize';
|
||||
import { sendRSVPEmailWithDebounce } from './core/emailer';
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -170,6 +171,7 @@ app.post('/rsvp/save', (req, res) => {
|
||||
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);
|
||||
sendRSVPEmailWithDebounce(data);
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
@@ -4423,6 +4423,10 @@ node-uuid@1.4.x, node-uuid@~1.4.4, node-uuid@~1.4.7:
|
||||
version "1.4.7"
|
||||
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:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
|
||||
|
||||
Reference in New Issue
Block a user