def test_add_address_to_mailchimp_existing(requests_mock, user): user.settings = {"email": {"marketing": True}} email1 = EmailAddress.objects.add_email(None, user, "*****@*****.**") email2 = EmailAddress.objects.add_email(None, user, "*****@*****.**") email2.set_as_primary() id = get_subscriber_hash("*****@*****.**") adapter = requests_mock.put(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id, json={ "email_address": "*****@*****.**", "unique_email_id": "12345", "status": "subscribed", "list_id": "test-list-key-id" }) signals.email_changed.send(sender=None, request=None, user=user, from_email_address=email1, to_email_address=email2) assert adapter.last_request.json() == { "email_address": "*****@*****.**", "status_if_new": "subscribed" }
def update_member(self, user: User) -> None: """ Update a single users data. """ subscription_status = self.get_status(user=user) try: response = self.client.lists.members.create_or_update( list_id=self.list_id, subscriber_hash=get_subscriber_hash(member_email=user.email), data={ 'email_address': user.email, 'status': subscription_status, 'status_if_new': subscription_status, 'merge_fields': { 'FNAME': user.first_name, 'LNAME': user.last_name, } }) logger.info(response) except requests.HTTPError: # This usually means that the person has unsubscribed via MailChimp. # If this is the case, unsubscribe the user - and don't submit this # to the API via signal! if not self.is_active_member(user=user): from .signals import update_newsletter_subscription post_save.disconnect(update_newsletter_subscription, sender=User) user.unsubscribe() post_save.connect(update_newsletter_subscription, sender=User)
def synchronize_mailchimp_email(client, list_id, email, merge_fields, email_type='Email'): '''Handles common exceptions thrown by Mailchimp when synchronizing. ''' try: # Create or update subscriber client.lists.members.create_or_update( list_id=list_id, subscriber_hash=get_subscriber_hash(email), data={ 'email_address': email, 'status': SUBSCRIBED, 'status_if_new': SUBSCRIBED, 'merge_fields': merge_fields }) except HTTPError as ex: # Handle Mailchimp complaining about potentially faulty data, don't synchronize if ex.response.status_code == 400 and 'fake' in ex.response.content: logger.warning( 'Unable to synchronize `%s %s`, they are suspected to have a fake email' % (email_type, email)) else: logger.warning( 'Unable to synchronize `%s %s`, an unknown error occured' % (email_type, email)) except ValueError: # Someone didn't give a valid email logger.warning('Unable to synchronize `%s %s`, invalid email' % (email_type, email))
def test_sync_premium_accounts_for_paypal_subscription_existing( requests_mock, user): id = get_subscriber_hash("*****@*****.**") current_time = datetime.now(tz=timezone.utc) agreement = _create_billing_agreement(user, current_time, current_time + timedelta(days=100)) EmailAddress.objects.add_email(None, user, "*****@*****.**") event = WebhookEvent(resource_type="agreement", resource=agreement.__dict__) _stub_stripe_api(requests_mock) members_endpoint = _stub_mailchimp_create_or_update( requests_mock, "*****@*****.**") tag_endpoint = requests_mock.post(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id + "/tags", json={}) group, created = Group.objects.get_or_create( name="mailchimp:premium-subscriber") group.user_set.add(user) sync_premium_accounts_for_paypal_subscription(None, event) assert members_endpoint.last_request is None assert tag_endpoint.last_request is None
def test_add_address_to_mailchimp(requests_mock, user): user.settings = {"email": {"marketing": True}} email = EmailAddress.objects.add_email(None, user, "*****@*****.**") email.set_as_primary() email.save() adapter = requests_mock.post( MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members", json={ "id": get_subscriber_hash("*****@*****.**"), "email_address": "*****@*****.**", "unique_email_id": "12345", "status": "subscribed", "list_id": "test-list-key-id" }) signals.email_changed.send(sender=None, request=None, user=user, from_email_address=None, to_email_address=email) assert adapter.last_request.json() == { "email_address": "*****@*****.**", "status": "subscribed" }
def test_sync_premium_accounts_for_stripe_subscription(requests_mock, user): user.settings = {"email": {"marketing": True}} user.save() id = get_subscriber_hash("*****@*****.**") customer = _create_stripe_customer(user) EmailAddress.objects.add_email(None, user, "*****@*****.**") event = _create_stripe_event(user) _stub_stripe_api(requests_mock, stripe_id=customer.stripe_id) members_endpoint = _stub_mailchimp_create_or_update( requests_mock, "*****@*****.**") tag_endpoint = requests_mock.post(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id + "/tags", json={}) sync_premium_accounts_for_stripe_subscription(event) assert members_endpoint.last_request.json() == { "email_address": "*****@*****.**", "status_if_new": "subscribed" } assert tag_endpoint.last_request.json() == { "tags": [{ "name": "Premium Subscriber", "status": "active" }] } group = Group.objects.get(name="mailchimp:premium-subscriber") assert user in group.user_set.all()
def test_sync_premium_accounts_for_stripe_subscription_existing( requests_mock, user): user.settings = {"email": {"marketing": True}} id = get_subscriber_hash("*****@*****.**") customer = _create_stripe_customer(user) EmailAddress.objects.add_email(None, user, "*****@*****.**") event = _create_stripe_event(user) _stub_stripe_api(requests_mock, stripe_id=customer.stripe_id) members_endpoint = _stub_mailchimp_create_or_update( requests_mock, "*****@*****.**") tag_endpoint = requests_mock.post(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id + "/tags", json={}) group, created = Group.objects.get_or_create( name="mailchimp:premium-subscriber") group.user_set.add(user) sync_premium_accounts_for_stripe_subscription(event) assert members_endpoint.last_request is None assert tag_endpoint.last_request is None
def post(self, request): marketing_prefs = request.POST.get("marketing", "") == "on" request.user.settings["email"] = { "marketing": marketing_prefs, "updated": timezone.now().isoformat(), } request.user.save() try: client = get_mailchimp_client() email = find_best_email_for_user(request.user) client.lists.members.create_or_update( settings.MAILCHIM_LIST_KEY_ID, get_subscriber_hash(email.email), { "email_address": email.email, "status_if_new": get_mailchimp_subscription_status(request.user) }) except Exception as e: log.warning("Failed to contact MailChimp API: %s" % e) influx_metric("mailchimp_request_failures", {"count": 1}) messages.info(request, _("Your email preferences have been saved.")) return redirect(self.success_url)
def update_mailchimp(modeladmin, request, queryset): valid_contacts = queryset.filter(email__isnull=False) client = MailChimp(mc_api=settings.MAILCHIMP_API, mc_user=settings.MAILCHIMP_USER) listas = client.lists.all(get_all=True, fields="lists.name,lists.id") lista = listas['lists'][0]['id'] for p in valid_contacts: user_hash = get_subscriber_hash(p.email) contact = { 'email_address': p.email, 'status_if_new': 'subscribed', 'merge_fields': { 'FNAME': p.nome, 'LNAME': p.sobrenome, 'CITY' : p.cidade, 'STATE' : p.estado, 'ORG' : p.entidade.first().nome if p.entidade.first() else '', 'SENSIBLE' : '', 'EVENTS' : '', 'PHONE' : p.telefone, 'ADDRESS' : p.endereco, 'GENDER' : p.sexo.nome if p.sexo else '', 'AGE' : p.idade() }, 'tags' : [], } contact['merge_fields'] = return_empty(contact['merge_fields']) resultado = client.lists.members.create_or_update(lista, user_hash, return_empty(contact)) modeladmin.message_user(request, '{} contatos atualizados.'.format(valid_contacts.count()))
def _publish_tag_changes(self, user, email_str, tags_to_add, tags_to_remove, verbose=False): list_key_id = settings.MAILCHIMP_LIST_KEY_ID email_hash = get_subscriber_hash(email_str) client = get_mailchimp_client() # We may never have seen this user's email address before or sent it to MailChimp, # so do a defensive subscriber creation. try: client.lists.members.create_or_update( list_key_id, email_hash, { "email_address": email_str, "status_if_new": get_mailchimp_subscription_status(user) }) influx_metric("mailchimp_requests", {"count": 1}, method="create_or_update") self.mailchimp_api_requests += 1 # Tell MailChimp to add any tags that we added locally. if len(tags_to_add) > 0: tag_names = list(map(lambda tag: tag.name, tags_to_add)) if verbose: print( f"Sending request to add tags {tag_names} to {email_str}" ) client.lists.members.tags.add(list_key_id, email_hash, tag_names) influx_metric("mailchimp_requests", {"count": 1}, method="add_tags") self.mailchimp_api_requests += 1 # Tell MailChimp to remove any tags that we removed locally. if len(tags_to_remove) > 0: tag_names = list(map(lambda tag: tag.name, tags_to_remove)) if verbose: print( f"Sending request to remove tags {tag_names} from {email_str}" ) client.lists.members.tags.delete(list_key_id, email_hash, tag_names) influx_metric("mailchimp_requests", {"count": 1}, method="delete_tags") self.mailchimp_api_requests += 1 except Exception as e: print("Failed to contact MailChimp API: %s" % e, flush=True) influx_metric("mailchimp_request_failures", {"count": 1})
def _validate_profile(self, payload): """ Returns an active Profile that matches the claims's user_id. """ user_id = payload.get("sub") try: auth_user = AuthUser.objects.get(user_id=user_id) profile = auth_user.profile except AuthUser.DoesNotExist: user_info = get_user_info(user_id) profile, is_new = get_or_create_safeish(Profile, email=user_info["email"]) if is_new is True: profile.first_name = user_info["first_name"] profile.last_name = user_info["last_name"] profile.save() if ( settings.MC_API_KEY is not None and settings.MC_USER is not None and settings.MC_LIST_ID is not None ): from mailchimp3 import MailChimp from mailchimp3.helpers import get_subscriber_hash # https://developer.mailchimp.com/documentation/mailchimp/guides/manage-subscribers-with-the # -mailchimp-api/ try: client = MailChimp( mc_api=settings.MC_API_KEY, mc_user=settings.MC_USER ) client.lists.members.create_or_update( settings.MC_LIST_ID, get_subscriber_hash(profile.email), { "email_address": profile.email, "status_if_new": "pending", "merge_fields": { "FNAME": profile.first_name, "LNAME": profile.last_name, "API": "yes", }, }, ) except: # Don't ever fail because subscription didn't work logger.error( "Unable to create mailchimp member {} {} <{}>".format( profile.first_name, profile.last_name, profile.email ) ) get_or_create_safeish(AuthUser, profile=profile, user_id=user_id) return profile
def unsubscribe_user( email_address, list_id=settings.MAILCHIMP_LIST_ID, ): client = get_client() client.lists.members.delete( list_id=list_id, subscriber_hash=get_subscriber_hash(email_address)) return
def is_active_member(self, user: User) -> bool: try: response = self.client.lists.members.get( list_id=self.list_id, subscriber_hash=get_subscriber_hash(member_email=user.email)) except requests.HTTPError: # Invalid request -> the user doesn't exist on MailChimp and # therefore is not subscribed. return False return response.get('status', '') == 'subscribed'
def _stub_mailchimp_create_or_update(requests_mock, email): id = get_subscriber_hash(email) return requests_mock.put(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id, json={ "id": id, "email_address": email, "unique_email_id": "12345", "status": "subscribed", "list_id": "test-list-key-id" })
def main_post(): # mc_user will be removed in v2.1.0 mc = MailChimp(mc_secret=app.config['MAILCHIMP_KEY'], mc_user='******') try: email = request.form.get('email') try: data = mc.lists.members.create_or_update( list_id=app.config['MAILCHIMP_LIST'], subscriber_hash=get_subscriber_hash(email), data={'email_address': email, 'status': 'subscribed', 'status_if_new': 'pending'} ) status = data.get('status') if status == 'pending': flash('Thanks for subscribing! You will receive a confirmation email shortly.') elif status == 'subscribed': flash('You were already subscribed! Thanks for checking back.') else: raise ValueError('Unexpected status %s' % status) except ValueError as e: # ugh, this library is awful app.logger.info('ValueError from mailchimp3 %s, assuming bad email: %r', e, email) flash("Your email address was not accepted - please check and try again.") # should also be changed to exceptions in v2.1.0 except HTTPError as e: if e.response.status_code != 400: raise data = e.response.json() title = data.get('title') if title == "Member In Compliance State": app.logger.info('Member in compliance state: %r', email) flash("""You've already been unsubscribed from our list, so we can't add you again. Please contact %s to update your settings.""" % app.config['TICKETS_EMAIL'][1]) elif title == 'Invalid Resource': app.logger.warn('Invalid Resource from MailChimp, assuming bad email: %r', email) flash("Your email address was not accepted - please check and try again.") else: app.logger.warn('MailChimp returned %s: %s', title, data.get('detail')) flash('Sorry, an error occurred: %s.' % (title or 'unknown')) except Exception as e: app.logger.error('Error subscribing: %r', e) flash('Sorry, an error occurred.') return redirect(url_for('.main'))
def save(self, request): email = self.cleaned_data['email'] mailchimp_settings = MailchimpSettings.for_site(request.site) client = mailchimp_settings.get_client() list_id = mailchimp_settings.newsletter_list hash = get_subscriber_hash(email) client.lists.members.create_or_update( list_id, hash, { 'email_address': email, 'status': 'subscribed', 'status_if_new': 'subscribed', })
def subscribe_user( email_address, list_id=settings.MAILCHIMP_LIST_ID, ): client = get_client() client.lists.members.create_or_update( list_id=list_id, subscriber_hash=get_subscriber_hash(email_address), data={ 'email_address': email_address, 'status_if_new': 'subscribed', }) return
def test_post_marketing_true_compliance(self, client, settings, user): user.emailaddress_set.add( EmailAddress.objects.create(user=user, email="*****@*****.**")) client.force_login(user, backend=settings.AUTHENTICATION_BACKENDS[0]) mock_mailchimp_client = Mock() create_or_update = mock_mailchimp_client.lists.members.create_or_update create_or_update.side_effect = MailChimpError({ "status": 400, "title": "Member In Compliance State" }) with patch("hsreplaynet.web.views.dashboard.get_mailchimp_client" ) as get_client: get_client.return_value = mock_mailchimp_client response = client.post("/account/email/preferences/", {"marketing": "on"}) assert response.status_code == 302 create_or_update.assert_called_once_with( "test-list-key-id", get_subscriber_hash("*****@*****.**"), { "email_address": "*****@*****.**", "status_if_new": "subscribed", "status": "subscribed" }) update = mock_mailchimp_client.lists.members.update update.assert_called_once_with("test-list-key-id", get_subscriber_hash("*****@*****.**"), { "email_address": "*****@*****.**", "status": "pending" })
def subscribe(): """Subscribe email address to the newsletter""" data = request.get_json() try: client = MailChimp(current_app.config.get("MAILCHIMP_API_KEY")) subscribe_data = { "email_address": data["email"], "status_if_new": "subscribed" } interest_ids = current_app.config.get("MAILCHIMP_INTEREST_IDS", []).split(",") if interest_ids: subscribe_data["interests"] = { interest_id: True for interest_id in interest_ids } client.lists.members.create_or_update( current_app.config.get("MAILCHIMP_LIST_ID"), get_subscriber_hash(data["email"]), data=subscribe_data) return jsonify({"result": "subscribed"}) except HTTPError as e: if e.response.status_code == 400: json = e.response.json() resp = json.get("errors") or json.get("detail") or json print(json.get("errors") or json.get("detail") or json) current_app.logger.error( "An HTTPError occurred subscribing email to MailChimp: {}". format(json.get("errors") or json.get("detail") or json)) except Exception as e: current_app.logger.error( "An {} occurred subscribing email to MailChimp: {}".format( e.__class__, e)) resp = "An error occurred: {} - {}".format(e.__class__, e) return jsonify({"result": resp}), 500
def add_address_to_mailchimp(request, user, from_email_address, to_email_address, **_): """Signal handler for the email_changed event to add or update emails in MailChimp""" client = get_mailchimp_client() try: if from_email_address: # If there was a previous email address, we may (or may not) have added it to # our list in MailChimp already, along with zero or more tags. So let's try to # update it via the subscriber hash of the _previous_ address and update the # email address to the _new_ address. client.lists.members.create_or_update( settings.MAILCHIMP_LIST_KEY_ID, get_subscriber_hash(from_email_address.email), { "email_address": to_email_address.email, "status_if_new": get_mailchimp_subscription_status(user) }) influx_metric("mailchimp_requests", {"count": 1}, method="create_or_update") else: # But if there was no previous primary address, just add a new list member. client.lists.members.create( settings.MAILCHIMP_LIST_KEY_ID, { "email_address": to_email_address.email, "status": get_mailchimp_subscription_status(user) }) influx_metric("mailchimp_requests", {"count": 1}, method="create") except Exception as e: log.warning("Failed to contact MailChimp API: %s" % e) influx_metric("mailchimp_request_failures", {"count": 1})
def test_sync_premium_accounts_for_paypal_subscription(requests_mock, user): user.settings = {"email": {"marketing": True}} user.save() id = get_subscriber_hash("*****@*****.**") current_time = datetime.now(tz=timezone.utc) agreement = _create_billing_agreement(user, current_time, current_time + timedelta(days=100)) EmailAddress.objects.add_email(None, user, "*****@*****.**") event = WebhookEvent(resource_type="agreement", resource=agreement.__dict__) _stub_stripe_api(requests_mock) members_endpoint = _stub_mailchimp_create_or_update( requests_mock, "*****@*****.**") tag_endpoint = requests_mock.post(MAILCHIMP_TEST_ENDPOINT + "lists/test-list-key-id/members/" + id + "/tags", json={}) sync_premium_accounts_for_paypal_subscription(None, event) assert members_endpoint.last_request.json() == { "email_address": "*****@*****.**", "status_if_new": "subscribed" } assert tag_endpoint.last_request.json() == { "tags": [{ "name": "Premium Subscriber", "status": "active" }] } group = Group.objects.get(name="mailchimp:premium-subscriber") assert user in group.user_set.all()
def _update_mailchimp_tags_for_premium_subscriber(user): email = find_best_email_for_user(user) if email: tag = PremiumSubscriberTag() if tag.should_apply_to(user) and tag.add_user_to_tag_group(user): client = get_mailchimp_client() list_key_id = settings.MAILCHIMP_LIST_KEY_ID email_hash = get_subscriber_hash(email.email) # At this point we have no idea whether the user's email address is already # registered with MailChimp, so do a defensive create-or-update to make sure # it's on the list before we give them the premium tag. try: client.lists.members.create_or_update( list_key_id, email_hash, { "email_address": email.email, "status_if_new": get_mailchimp_subscription_status(user), }) influx_metric("mailchimp_requests", {"count": 1}, method="create_or_update") # ...then assign the premium tag. client.lists.members.tags.add(list_key_id, email_hash, tag.name) influx_metric("mailchimp_requests", {"count": 1}, method="add_tags") except Exception as e: log.warning("Failed to contact MailChimp API: %s" % e) influx_metric("mailchimp_request_failures", {"count": 1})
def main_post(): mc = MailChimp(mc_api=app.config["MAILCHIMP_KEY"]) try: email = request.form.get("email") try: data = mc.lists.members.create_or_update( list_id=app.config["MAILCHIMP_LIST"], subscriber_hash=get_subscriber_hash(email), data={ "email_address": email, "status": "subscribed", "status_if_new": "pending", }, ) status = data.get("status") if status == "pending": flash( "Thanks for subscribing! You will receive a confirmation email shortly." ) elif status == "subscribed": flash("You were already subscribed! Thanks for checking back.") else: raise ValueError("Unexpected status %s" % status) except ValueError as e: # ugh, this library is awful app.logger.info( "ValueError from mailchimp3 %s, assuming bad email: %r", e, email) flash( "Your email address was not accepted - please check and try again." ) # should also be changed to exceptions in v2.1.0 except HTTPError as e: if e.response.status_code != 400: raise data = e.response.json() title = data.get("title") if title == "Member In Compliance State": app.logger.info("Member in compliance state: %r", email) flash( """You've already been unsubscribed from our list, so we can't add you again. Please contact %s to update your settings.""" % app.config["TICKETS_EMAIL"][1]) elif title == "Invalid Resource": app.logger.warn( "Invalid Resource from MailChimp, assuming bad email: %r", email) flash( "Your email address was not accepted - please check and try again." ) else: app.logger.warn("MailChimp returned %s: %s", title, data.get("detail")) flash("Sorry, an error occurred: %s." % (title or "unknown")) except Exception as e: app.logger.error("Error subscribing: %r", e) flash("Sorry, an error occurred.") return redirect(url_for(".main"))
def _construct_endpoint(self, email): return "%slists/123456/members/%s/tags" % ( self.MAILCHIMP_TEST_ENDPOINT, get_subscriber_hash(email) )
def main_post(): # mc_user will be removed in v2.1.0 mc = MailChimp(mc_secret=app.config['MAILCHIMP_KEY'], mc_user='******') try: email = request.form.get('email') try: data = mc.lists.members.create_or_update( list_id=app.config['MAILCHIMP_LIST'], subscriber_hash=get_subscriber_hash(email), data={ 'email_address': email, 'status': 'subscribed', 'status_if_new': 'pending' }) status = data.get('status') if status == 'pending': flash( 'Thanks for subscribing! You will receive a confirmation email shortly.' ) elif status == 'subscribed': flash('You were already subscribed! Thanks for checking back.') else: raise ValueError('Unexpected status %s' % status) except ValueError as e: # ugh, this library is awful app.logger.info( 'ValueError from mailchimp3 %s, assuming bad email: %r', e, email) flash( "Your email address was not accepted - please check and try again." ) # should also be changed to exceptions in v2.1.0 except HTTPError as e: if e.response.status_code != 400: raise data = e.response.json() title = data.get('title') if title == "Member In Compliance State": app.logger.info('Member in compliance state: %r', email) flash( """You've already been unsubscribed from our list, so we can't add you again. Please contact %s to update your settings.""" % app.config['TICKETS_EMAIL'][1]) elif title == 'Invalid Resource': app.logger.warn( 'Invalid Resource from MailChimp, assuming bad email: %r', email) flash( "Your email address was not accepted - please check and try again." ) else: app.logger.warn('MailChimp returned %s: %s', title, data.get('detail')) flash('Sorry, an error occurred: %s.' % (title or 'unknown')) except Exception as e: app.logger.error('Error subscribing: %r', e) flash('Sorry, an error occurred.') return redirect(url_for('.main'))
def post(self, request): marketing_prefs = request.POST.get("marketing", "") == "on" client = get_mailchimp_client() try: request.user.settings["email"] = { "marketing": marketing_prefs, "updated": timezone.now().isoformat(), } status = get_mailchimp_subscription_status(request.user) email = find_best_email_for_user(request.user) try: client.lists.members.create_or_update( settings.MAILCHIMP_LIST_KEY_ID, get_subscriber_hash(email.email), { "email_address": email.email, "status_if_new": status, "status": status }) except MailChimpError as e: # The request to Mailchimp may fail if the user unsubscribed via the # Mailchimp interface (through a link in an email footer, e.g.). In this # case, they can only resubscribe through a confirmation email. Setting the # user's status to 'pending' will trigger the confirmation email to be sent. args = e.args[0] if (args.get("status") == 400 and args.get("title") == "Member In Compliance State"): client.lists.members.update( settings.MAILCHIMP_LIST_KEY_ID, get_subscriber_hash(email.email), { "email_address": email.email, "status": "pending" }) influx_metric("mailchimp_requests", {"count": 1}, method="update") return self._redirect_with_message( request, _("Check your inbox! We've sent an email to confirm your subscription preferences." )) else: self._log_mailchimp_error(e) return self._redirect_with_message( request, _("Failed to save your email preferences. Please try again later!" )) except Exception as e: self._log_mailchimp_error(e) return self._redirect_with_message( request, _("Failed to save your email preferences. Please try again later!" )) # Only save the user record if we were actually able to sync the marketing # preferences with Mailchimp. request.user.save() influx_metric("mailchimp_requests", {"count": 1}, method="create_or_update") return self._redirect_with_message( request, _("Your email preferences have been saved."))