Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
refactor!: Remove sailthru and braze specific modules (#134)
Browse files Browse the repository at this point in the history
Instead, use a more generically named email module that abstracts
whatever provider we're using under the covers:

ecommerce_worker.email.v1.api.*

The public API are four celery tasks and one bounce-check method:
- send_code_assignment_nudge_email
- send_offer_assignment_email
- send_offer_update_email
- send_offer_usage_email
- did_email_bounce
  • Loading branch information
mikix authored May 14, 2021
1 parent 107e2ec commit 7a821b7
Show file tree
Hide file tree
Showing 34 changed files with 1,023 additions and 2,151 deletions.
54 changes: 6 additions & 48 deletions ecommerce_worker/configuration/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# CELERY
# Default broker URL. See http://celery.readthedocs.org/en/latest/configuration.html#broker-url.
# Default broker URL. See http://celery.readthedocs.org/en/4.0/userguide/configuration.html#std:setting-broker_url.
BROKER_URL = 'redis://:celery@127.0.0.1:6379'

# Disable connection pooling. Connections may be severed by load balancers.
Expand All @@ -15,22 +15,22 @@
BROKER_HEARTBEAT_CHECKRATE = 2

# Backend used to store task results.
# See http://celery.readthedocs.org/en/latest/configuration.html#celery-result-backend.
# See http://celery.readthedocs.org/en/4.0/userguide/configuration.html#std:setting-result_backend.
CELERY_RESULT_BACKEND = None

# A sequence of modules to import when the worker starts.
# See http://celery.readthedocs.org/en/latest/configuration.html#celery-imports.
# See http://celery.readthedocs.org/en/4.0/userguide/configuration.html#std:setting-imports.
CELERY_IMPORTS = (
'ecommerce_worker.email.v1.tasks',
'ecommerce_worker.fulfillment.v1.tasks',
'ecommerce_worker.sailthru.v1.tasks',
)

DEFAULT_PRIORITY_QUEUE = 'ecommerce.default'
CELERY_DEFAULT_EXCHANGE = 'ecommerce'
CELERY_DEFAULT_ROUTING_KEY = 'ecommerce'
CELERY_DEFAULT_QUEUE = DEFAULT_PRIORITY_QUEUE
# Prevent Celery from removing handlers on the root logger. Allows setting custom logging handlers.
# See http://celery.readthedocs.org/en/latest/configuration.html#celeryd-hijack-root-logger.
# See http://celery.readthedocs.org/en/4.0/userguide/configuration.html#std:setting-worker_hijack_root_logger.
CELERYD_HIJACK_ROOT_LOGGER = False

# Specify allowed serializers that are consistent with Celery 3 defaults
Expand Down Expand Up @@ -66,7 +66,7 @@
# .. toggle_name: BRAZE['BRAZE_ENABLE']
# .. toggle_implementation: PythonConstant
# .. toggle_default: False
# .. toggle_description: Toggle for allowing emails to be sent via Braze instead of sailthru
# .. toggle_description: Toggle for allowing emails to be sent via Braze
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-02-09
# .. toggle_target_removal_date: None
Expand All @@ -88,46 +88,4 @@
# Retry settings for Braze celery tasks
'BRAZE_RETRY_SECONDS': 3600,
'BRAZE_RETRY_ATTEMPTS': 6,
}

# Settings for Sailthru email marketing integration
SAILTHRU = {
# Set to false to ignore Sailthru events
'SAILTHRU_ENABLE': False,

# Template used when user upgrades to verified
'SAILTHRU_UPGRADE_TEMPLATE': None,

# Template used with user purchases a course
'SAILTHRU_PURCHASE_TEMPLATE': None,

# Template used with user enrolls in a free course
'SAILTHRU_ENROLL_TEMPLATE': None,

# Abandoned cart template
'SAILTHRU_ABANDONED_CART_TEMPLATE': None,

# minutes to delay before abandoned cart message
'SAILTHRU_ABANDONED_CART_DELAY': 60,

# Sailthru key and secret required for integration
'SAILTHRU_KEY': 'sailthru key here',
'SAILTHRU_SECRET': 'sailthru secret here',

# Retry settings for Sailthru celery tasks
'SAILTHRU_RETRY_SECONDS': 3600,
'SAILTHRU_RETRY_ATTEMPTS': 6,

# ttl for cached course content from Sailthru (in seconds)
'SAILTHRU_CACHE_TTL_SECONDS': 3600,

# dummy price for audit/honor (i.e., if cost = 0)
# Note: setting this value to 0 skips Sailthru calls for free transactions
'SAILTHRU_MINIMUM_COST': 100,

# Transactional email template name map
'templates': {
'assignment_email': 'Offer Assignment Email',
'enterprise_portal_email': 'Enterprise Portal Email',
}
}
24 changes: 0 additions & 24 deletions ecommerce_worker/configuration/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,3 @@

# SITE-SPECIFIC CONFIGURATION OVERRIDES
SITE_OVERRIDES = {}

# Sailthru support unit test settings
SAILTHRU.update({
'SAILTHRU_ENABLE': True,
'SAILTHRU_UPGRADE_TEMPLATE': 'upgrade_template',
'SAILTHRU_PURCHASE_TEMPLATE': 'purchase_template',
'SAILTHRU_ENROLL_TEMPLATE': 'enroll_template',
'SAILTHRU_ABANDONED_CART_TEMPLATE': 'abandoned_template',
'SAILTHRU_KEY': 'key',
'SAILTHRU_SECRET': 'secret',
})

# Braze unit test settings
BRAZE.update({
'BRAZE_ENABLE': False,
'BRAZE_REST_API_KEY': 'rest_api_key',
'BRAZE_WEBAPP_API_KEY': 'webapp_api_key',
})

# Sailthru support unit test settings with override
TEST_SITE_OVERRIDES = {
'test_site': { 'SAILTHRU': dict(SAILTHRU,
SAILTHRU_UPGRADE_TEMPLATE='site_upgrade_template')}
}
File renamed without changes.
17 changes: 17 additions & 0 deletions ecommerce_worker/email/v1/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Public API for ecommerce_worker.email.v1"""

# Disable unused imports because these imports are meant for external consumption
# pylint: disable=unused-import

# Celery tasks
from .tasks import (
send_code_assignment_nudge_email,
send_offer_assignment_email,
send_offer_update_email,
send_offer_usage_email,
)

# Normal helper methods
from .utils import (
did_email_bounce,
)
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
"""
Braze Client functions.
Braze Client functions.
"""

from __future__ import absolute_import
from urllib.parse import urlencode, urljoin

import copy
import json
import requests

from urllib.parse import urlencode, urljoin

from celery.utils.log import get_task_logger

from ecommerce_worker.braze.v1.exceptions import (
from ecommerce_worker.email.v1.braze.exceptions import (
ConfigurationError,
BrazeNotEnabled,
BrazeClientError,
Expand All @@ -24,6 +21,11 @@
log = get_task_logger(__name__)


def is_braze_enabled(site_code) -> bool:
config = get_braze_configuration(site_code)
return bool(config.get('BRAZE_ENABLE'))


def get_braze_configuration(site_code):
"""
Returns the Braze configuration for the specified site.
Expand Down
154 changes: 154 additions & 0 deletions ecommerce_worker/email/v1/braze/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""
This file contains celery task functionality for braze.
"""

from celery.utils.log import get_task_logger

from ecommerce_worker.email.v1.braze.client import get_braze_client, get_braze_configuration
from ecommerce_worker.email.v1.braze.exceptions import BrazeError, BrazeRateLimitError, BrazeInternalServerError
from ecommerce_worker.email.v1.utils import update_assignment_email_status

logger = get_task_logger(__name__)


def send_offer_assignment_email_via_braze(self, user_email, offer_assignment_id, subject, email_body, sender_alias,
site_code):
"""
Sends the offer assignment email via Braze.
Args:
self: Ignore.
user_email (str): Recipient's email address.
offer_assignment_id (str): Key of the entry in the offer_assignment model.
subject (str): Email subject.
email_body (str): The body of the email.
sender_alias (str): Enterprise Customer sender alias used as From Name.
site_code (str): Identifier of the site sending the email.
base_enterprise_url (str): Url for the enterprise learner portal.
"""
config = get_braze_configuration(site_code)
try:
user_emails = [user_email]
braze_client = get_braze_client(site_code)
response = braze_client.send_message(
email_ids=user_emails,
subject=subject,
body=email_body,
sender_alias=sender_alias
)
if response and response['success']:
dispatch_id = response['dispatch_id']
if update_assignment_email_status(offer_assignment_id, dispatch_id, 'success'):
logger.info('[Offer Assignment] Offer assignment notification sent with message --- '
'{message}'.format(message=email_body))
else:
logger.exception(
'[Offer Assignment] An error occurred while updating email status data for '
'offer {token_offer} and email {token_email} via the ecommerce API.'.format(
token_offer=offer_assignment_id,
token_email=user_email,
)
)
except (BrazeRateLimitError, BrazeInternalServerError):
raise self.retry(countdown=config.get('BRAZE_RETRY_SECONDS'),
max_retries=config.get('BRAZE_RETRY_ATTEMPTS'))
except BrazeError:
logger.exception(
('[Offer Assignment] Error in offer assignment notification with message --- '
'{message}'.format(message=email_body)
)
)


def send_offer_update_email_via_braze(self, user_email, subject, email_body, sender_alias, site_code):
"""
Sends the offer emails after assignment via braze, either for revoking or reminding.
Args:
self: Ignore.
user_email (str): Recipient's email address.
subject (str): Email subject.
email_body (str): The body of the email.
site_code (str): Identifier of the site sending the email.
sender_alias (str): Enterprise Customer sender alias used as From Name.
"""
config = get_braze_configuration(site_code)
try:
user_emails = [user_email]
braze_client = get_braze_client(site_code)
braze_client.send_message(
email_ids=user_emails,
subject=subject,
body=email_body,
sender_alias=sender_alias
)
except (BrazeRateLimitError, BrazeInternalServerError):
raise self.retry(countdown=config.get('BRAZE_RETRY_SECONDS'),
max_retries=config.get('BRAZE_RETRY_ATTEMPTS'))
except BrazeError:
logger.exception(
'[Offer Assignment] Error in offer update notification with message --- '
'{message}'.format(message=email_body)
)


def send_offer_usage_email_via_braze(self, emails, subject, email_body, site_code):
"""
Sends the offer usage email via braze.
Args:
self: Ignore.
emails (str): comma separated emails.
subject (str): Email subject.
email_body (str): The body of the email.
site_code (str): Identifier of the site sending the email.
"""
config = get_braze_configuration(site_code)
try:
user_emails = list(emails.strip().split(","))
braze_client = get_braze_client(site_code)
braze_client.send_message(
email_ids=user_emails,
subject=subject,
body=email_body
)
except (BrazeRateLimitError, BrazeInternalServerError):
raise self.retry(countdown=config.get('BRAZE_RETRY_SECONDS'),
max_retries=config.get('BRAZE_RETRY_ATTEMPTS'))
except BrazeError:
logger.exception(
'[Offer Usage] Error in offer usage notification with message --- '
'{message}'.format(message=email_body)
)


def send_code_assignment_nudge_email_via_braze(self, email, subject, email_body, sender_alias, site_code):
"""
Sends the code assignment nudge email via braze.
Args:
self: Ignore.
email (str): Recipient's email address.
subject (str): Email subject.
email_body (str): The body of the email.
sender_alias (str): Enterprise Customer sender alias used as From Name.
site_code (str): Identifier of the site sending the email.
"""
config = get_braze_configuration(site_code)
try:
user_emails = [email]
braze_client = get_braze_client(site_code)
braze_client.send_message(
email_ids=user_emails,
subject=subject,
body=email_body,
sender_alias=sender_alias
)
except (BrazeRateLimitError, BrazeInternalServerError):
raise self.retry(countdown=config.get('BRAZE_RETRY_SECONDS'),
max_retries=config.get('BRAZE_RETRY_ATTEMPTS'))
except BrazeError:
logger.exception(
'[Code Assignment Nudge Email] Error in offer nudge notification with message --- '
'{message}'.format(message=email_body)
)
Loading

0 comments on commit 7a821b7

Please sign in to comment.