mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
Newsletter: Capture emails from interested users on registration
Adds an an opt-in checkbox to the registration flow for the upvoted newsletter, a project Alexis and Heath are working on. This does not associate any data with the user's account, it just sends their email address to the campaign monitor API if they opted in.
This commit is contained in:
@@ -39,6 +39,8 @@ paypal_webhook =
|
||||
coinbase_webhook =
|
||||
# secret for communicating with RedditGifts (optional payment processor)
|
||||
redditgifts_webhook =
|
||||
# The campaign monitor API key for the newsletter
|
||||
newsletter_api_key =
|
||||
|
||||
[DEFAULT]
|
||||
############################################ SITE-SPECIFIC OPTIONS
|
||||
@@ -287,6 +289,10 @@ embedly_api_key =
|
||||
autoexpand_media_types = liveupdate
|
||||
|
||||
|
||||
############################################ NEWSLETTER
|
||||
# The list ID within campaign monitor to be altering
|
||||
newsletter_list_id =
|
||||
|
||||
############################################ QUOTAS
|
||||
# quota for various types of relations creatable in subreddits
|
||||
sr_banned_quota = 10000
|
||||
|
||||
@@ -97,7 +97,7 @@ from r2.lib.db import queries
|
||||
from r2.lib import media
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib import promote
|
||||
from r2.lib import tracking, emailer
|
||||
from r2.lib import tracking, emailer, newsletter
|
||||
from r2.lib.subreddit_search import search_reddits
|
||||
from r2.lib.log import log_text
|
||||
from r2.lib.filters import safemarkdown
|
||||
@@ -278,11 +278,19 @@ class ApiController(RedditController):
|
||||
return {}
|
||||
|
||||
@csrf_exempt
|
||||
@json_validate(email=ValidEmail("email"))
|
||||
def POST_check_email(self, responder, email):
|
||||
@json_validate(email=ValidEmail("email"),
|
||||
newsletter_subscribe=VBoolean("newsletter_subscribe", default=False))
|
||||
def POST_check_email(self, responder, email, newsletter_subscribe):
|
||||
"""
|
||||
Check whether an email is valid.
|
||||
Check whether an email is valid. Allows blank emails.
|
||||
|
||||
Additionally checks if a newsletter is requested, and will be strict
|
||||
on blank emails if so.
|
||||
"""
|
||||
if feature.is_enabled('newsletter') and newsletter_subscribe and not email:
|
||||
c.errors.add(errors.NEWSLETTER_NO_EMAIL, field="email")
|
||||
responder.has_errors("email", errors.NEWSLETTER_NO_EMAIL)
|
||||
return
|
||||
|
||||
if not (responder.has_errors("email", errors.BAD_EMAIL)):
|
||||
# Pylons does not handle 204s correctly.
|
||||
@@ -650,6 +658,16 @@ class ApiController(RedditController):
|
||||
responder.has_errors('ratelimit', errors.RATELIMIT) or
|
||||
(not g.disable_captcha and bad_captcha)):
|
||||
|
||||
newsletter_subscribe = False
|
||||
if feature.is_enabled('newsletter'):
|
||||
# Todo: add to validatedForm when feature is released
|
||||
vnewsletter = VBoolean('newsletter_subscribe', default=False)
|
||||
newsletter_subscribe = vnewsletter.run(request.params.get('newsletter_subscribe'))
|
||||
if newsletter_subscribe and not email:
|
||||
c.errors.add(errors.NEWSLETTER_NO_EMAIL, field="email")
|
||||
form.has_errors("email", errors.NEWSLETTER_NO_EMAIL)
|
||||
return
|
||||
|
||||
user = register(name, password, request.ip)
|
||||
VRatelimit.ratelimit(rate_ip = True, prefix = "rate_register_")
|
||||
|
||||
@@ -673,6 +691,13 @@ class ApiController(RedditController):
|
||||
if any(reject):
|
||||
return
|
||||
|
||||
if feature.is_enabled('newsletter'):
|
||||
if newsletter_subscribe and email:
|
||||
try:
|
||||
newsletter.add_subscriber(email, source="register")
|
||||
except newsletter.NewsletterError as e:
|
||||
g.log.warning("Failed to subscribe: %r" % e)
|
||||
|
||||
self._login(responder, user, rem)
|
||||
|
||||
@csrf_exempt
|
||||
|
||||
@@ -92,6 +92,7 @@ error_list = dict((
|
||||
('BAD_EMAILS', _('the following emails are invalid: %(emails)s')),
|
||||
('NO_EMAILS', _('please enter at least one email address')),
|
||||
('TOO_MANY_EMAILS', _('please only share to %(num)s emails at a time.')),
|
||||
('NEWSLETTER_NO_EMAIL', _('where should we send that weekly newsletter?')),
|
||||
('OVERSOLD', _('that subreddit has already been oversold on %(start)s to %(end)s. Please pick another subreddit or date.')),
|
||||
('OVERSOLD_DETAIL', _("We have insufficient inventory to fulfill your requested budget, target, and dates. Only %(available)s impressions available on %(target)s from %(start)s to %(end)s.")),
|
||||
('BAD_DATE', _('please provide a date of the form mm/dd/yyyy')),
|
||||
|
||||
75
r2/r2/lib/newsletter.py
Normal file
75
r2/r2/lib/newsletter.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# The contents of this file are subject to the Common Public Attribution
|
||||
# License Version 1.0. (the "License"); you may not use this file except in
|
||||
# compliance with the License. You may obtain a copy of the License at
|
||||
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
# software over a computer network and provide for limited attribution for the
|
||||
# Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
# with Exhibit B.
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
# the specific language governing rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is reddit.
|
||||
#
|
||||
# The Original Developer is the Initial Developer. The Initial Developer of
|
||||
# the Original Code is reddit Inc.
|
||||
#
|
||||
# All portions of the code written by reddit are Copyright (c) 2006-2015 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from pylons import g
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
BASE_URL = "https://api.createsend.com/api/v3.1/"
|
||||
API_KEY = g.secrets['newsletter_api_key']
|
||||
LIST_ID = g.newsletter_list_id
|
||||
|
||||
|
||||
class NewsletterError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def add_subscriber(email, source=""):
|
||||
"""Given an email, add this user to our upvoted newsletter.
|
||||
|
||||
Optionally, also provide a source parameter describing where the subscribe
|
||||
came from - like "register".
|
||||
|
||||
If the email was unable to be added, throws `NewsletterError`. Returns
|
||||
`True` on success.
|
||||
|
||||
This should be used sparingly and outside of high traffic areas, as it
|
||||
requires a network call.
|
||||
"""
|
||||
if not API_KEY or not LIST_ID:
|
||||
raise NewsletterError("Unable to subscribe user %s to newsletter. "
|
||||
"API key or list ID not set." % email)
|
||||
|
||||
params = {
|
||||
"EmailAddress": email
|
||||
}
|
||||
if source:
|
||||
params["CustomFields"] = [{"Key": "source", "Value": source}]
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
"%s/subscribers/%s.json" % (BASE_URL, LIST_ID),
|
||||
json.dumps(params),
|
||||
timeout=3,
|
||||
auth=(API_KEY, 'x'),
|
||||
)
|
||||
except requests.exception.Timeout:
|
||||
raise NewsletterError("Unable to subscribe user %s to newsletter. "
|
||||
"Request timed out." % email)
|
||||
else:
|
||||
if r.status_code == 201:
|
||||
return True
|
||||
else:
|
||||
raise NewsletterError("Could not subscribe user %s to "
|
||||
"newsletter. Status code: %s" %
|
||||
(email, r.status_code))
|
||||
@@ -30,6 +30,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c-submit-group {
|
||||
margin-top: @input-height-base;
|
||||
}
|
||||
|
||||
.c-radio,
|
||||
.c-checkbox {
|
||||
position: relative;
|
||||
|
||||
@@ -112,17 +112,29 @@
|
||||
data-validate-on="change blur">
|
||||
</%call>
|
||||
%endif
|
||||
<div class="c-checkbox c-input-height">
|
||||
<label for="rem_${op}" class="remember">
|
||||
<input type="checkbox" name="rem" id="rem_${op}"
|
||||
tabindex="${tabindex}">
|
||||
<div class="c-checkbox">
|
||||
<input type="checkbox" name="rem" id="rem_${op}" tabindex="${tabindex}">
|
||||
<label for="rem_${op}">
|
||||
${_('remember me')}
|
||||
</label>
|
||||
%if not register:
|
||||
<a href="/password" class="c-pull-right">${_('reset password')}</a>
|
||||
%endif
|
||||
</div>
|
||||
<div class="c-clearfix">
|
||||
%if register and feature.is_enabled('newsletter'):
|
||||
<div class="c-checkbox">
|
||||
<input type="checkbox" name="newsletter_subscribe" id="newsletter_subscribe" tabindex="${tabindex}"
|
||||
data-validate-url="/api/check_email.json"
|
||||
data-validate-on="change blur"
|
||||
data-validate-with="email"
|
||||
>
|
||||
<label for="newsletter_subscribe">
|
||||
${_('get the best of reddit emailed to you once a week.')} 
|
||||
<a href="/newsletter" target="_blank">${_('learn more')}</a>
|
||||
</label>
|
||||
</div>
|
||||
%endif
|
||||
<div class="c-clearfix c-submit-group">
|
||||
<span class="c-form-throbber"></span>
|
||||
<button type="submit" class="c-btn c-btn-primary c-pull-right" tabindex="${tabindex}">
|
||||
${register and _("create account") or _("sign in")}
|
||||
|
||||
Reference in New Issue
Block a user