Skip to content
Permalink
Browse files

Added mechanism to cancel a newsletter subscription

  • Loading branch information...
davidmerfield committed Mar 13, 2019
1 parent 57a67fe commit 2bd8d14e7882606d06d83d5593e1d6db908876b1
@@ -40,21 +40,93 @@ news.get("/sign-up", function(req, res) {
res.render("news/sign-up");
});

news.get("/cancel", function(req, res) {
if (req.session && req.session.newsletter_email) {
res.locals.email = req.session.newsletter_email;
delete req.session.newsletter_email;
}

res.render("news/cancel");
});

var listKey = "newsletter:list";
var TTL = 60 * 60 * 24; // 1 day in seconds

function confirmationKey(guid) {
return "newsletter:confirm:" + guid;
}

var listKey = "newsletter:list";
var TTL = 60 * 60 * 24; // 1 day in seconds
function cancellationKey(guid) {
return "newsletter:cancel:" + guid;
}

function confirmationLink(guid) {
return "https://" + config.host + "/news/confirm/" + guid;
}

function cancelLink (email) {
return 'https://' + config.host + '/news/cancel/' + hash(email);
function cancellationLink(guid) {
return "https://" + config.host + "/news/cancel/" + guid;
}

news.post("/cancel", parse, function(req, res, next) {
var cancel, email, locals;
var guid = uuid();

if (!req.body || !req.body.email) {
return next(new Error("No email"));
}

email = req.body.email.trim().toLowerCase();
guid = guid.split("-").join("");
guid = encodeURIComponent(guid);
cancel = cancellationLink(guid);
locals = { email: email, cancel: cancel };

client.sismember(listKey, email, function(err, stat) {
if (err || !stat) return next(err || new Error("No subscription found"));

client.setex(cancellationKey(guid), TTL, email, function(err) {
if (err) return next(err);

helper.email.NEWSLETTER_CANCELLATION_CONFIRMATION(null, locals, function(
err
) {
if (err) return next(err);

req.session.newsletter_email = email;
res.redirect("/news/cancel");
});
});
});
});

news.get("/cancel/:guid", function(req, res, next) {
var guid = decodeURIComponent(req.params.guid);

client.get(cancellationKey(guid), function(err, email) {
if (err || !email) return next(err || new Error("No email"));

client.srem(listKey, email, function(err) {
if (err) return next(err);

var locals = { email: email };

helper.email.NEWSLETTER_CANCELLATION_CONFIRMED(null, locals, function(
err
) {
if (err) return next(err);

client.del(cancellationKey(guid), function(err) {
if (err) return next(err);

res.locals.email = email;
res.render("news/cancelled");
});
});
});
});
});

news.get("/confirm/:guid", function(req, res, next) {
var guid = decodeURIComponent(req.params.guid);

@@ -64,14 +136,21 @@ news.get("/confirm/:guid", function(req, res, next) {
client.sadd(listKey, email, function(err) {
if (err) return next(err);

var locals = { email: email, cancel: cancelLink(email) };
var locals = {
email: email,
cancel: "https://" + config.host + "/news/cancel"
};

helper.email.NEWSLETTER_SUBSCRIPTION_CONFIRMED(null, locals, function(
err
) {
if (err) return next(err);
res.locals.email = email;
res.render("news/confirmed");

client.del(confirmationKey(guid), function(err) {
if (err) return next(err);
res.locals.email = email;
res.render("news/confirmed");
});
});
});
});
@@ -0,0 +1,81 @@
<style type="text/css">
input[type="submit"] {
background: #333;
padding: 0.75em 1em;
color: white;
border: none;
border-bottom: 2px solid #000;
cursor: pointer;
font-size: 16px;
font-weight: 500;
margin: 2px 0
}
input[type="submit"]:focus {
border: none;
position: relative;
top: 2px
}
label {
font-size: 14px;
color: #666
}
/* turn into tooltip */
label em {
display: none;
}
input[name="email"],
input[name="password"] {
width: 100%;
display: block;
}
input[name="number"] {
width: 100%;
display: block;
}
input[name="cvc"] {
display: block;
}
input[type="text"],
input[type="password"] {
padding: 4px 3px;
margin: 2px 0;
font-size: 17px;
padding: 10px;
font-family: inherit;
z-index: 1;
position: relative;
border: 1px solid #cccbca;
border-top-color: rgba(0, 0, 0, .25);
border-bottom-color: rgba(0, 0, 0, .15);
box-sizing: border-box;
background: 0 0;
box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, .075);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
}
</style>
<h1>Cancel newsletter subscription</h1>
{{^email}}
<p>You can immediately cancel your subscription to Blot's quarterly newsletter. You will not recieve any future newsletters.</p>

<p>
<form action="/news/cancel" method="POST" style="max-width:24em;">
<label for="email">Your email</label>
<input type="text" required id="email" name="email">
<br>
<input type="submit" value="Cancel newsletter subscription">
</div>
</form>
{{/email}}

{{#email}}
<p>A confirmation email has been sent to <b>{{.}}</b>. Please click the link inside to cancel your subscription to Blot's newsletter.</p>
{{/email}}
@@ -0,0 +1 @@
<p>You have cancelled <b>{{email}}</b>'s subscription to Blot's newsletter.</p>
@@ -40,6 +40,8 @@ var MESSAGES = [
"NETWORK_ERROR",
"NEWSLETTER_SUBSCRIPTION_CONFIRMED",
"NEWSLETTER_SUBSCRIPTION_CONFIRMATION",
"NEWSLETTER_CANCELLATION_CONFIRMED",
"NEWSLETTER_CANCELLATION_CONFIRMATION",
"NO_SPACE",
"OVERDUE",
"OVERDUE_CLOSURE",
@@ -0,0 +1,5 @@
Do you want to cancel your subscription to Blot's newsletter?

[Cancel your newsletter subscription]({{{cancel}}})

Sincerely, David
@@ -0,0 +1,5 @@
Newsletter subscription cancelled

You have cancelled your subscription to Blot's newsletter. Please respond directly to this email if you have any questions.

Sincerely, David
@@ -1,3 +1,7 @@
Do you want to subscribe to Blot's newsletter?

[Confirm your subscription]({{{confirm}}})

Please respond directly to this email if you have any questions.

Sincerely, David
@@ -1,3 +1,5 @@
You have subscribed to Blot's newsletter

You can [cancel your subscription]({{{cancel}}}) to this newsletter at at point.
You can [cancel your subscription]({{{cancel}}}) to this newsletter at any point. Please respond directly to this email if you have any questions.

Sincerely, David

0 comments on commit 2bd8d14

Please sign in to comment.
You can’t perform that action at this time.