Dynamic Pacing Schedules
========================

The Schedules app allows course teams to automatically email learners in
self-paced courses. The emails are designed to keep learners engaged
with a course. Learners receive these messages at important milestones
in their learning.

The author of a self-paced course opts learners into one of two
“Schedule Experiences”. Learners either receive Weekly Course Highlight
Messages, or a combination of Recurring Nudges and Upgrade Reminders.

The app can send all three message types “out of the box”

-  Recurring Nudges
-  Upgrade Reminders
-  Weekly Course Highlight Messages

Recurring Nudges encourage learners to return to self-paced courses at
regular intervals. The app sends nudges three days and ten days after a
learner enrolls in a course.

Upgrade Reminders ask learners to purchase their course’s Verified
certificate. The reminders are sent two days before their course’s upgrade
deadline, or two days before the course’s end date (whichever date occurs
sooner).

Weekly Course Highlight Messages tell learners what to look forward to in the
coming week of a course. Course authors provide “section highlights” when
authoring a course in Studio. The app sends a weekly email with these
highlights listed in the message.

The app introduces a Schedule object to the edX codebase. Learners receive
Schedules when they enroll in self-paced courses. The app uses Schedules to
determine which learners to message.

In the future, the Schedules app may be extended or changed to support more
complicated communication patterns. For example, something that takes into
account the user's progress through the course.

Glossary
--------

-  **Schedule**: Stores the day a learner enrolls in a course and the
   learner's "upgrade deadline". This information allows us to personalize
   learners' experiences in self-paced courses so that events happen relative to
   the learner's schedule, not the course's schedule.

-  **Schedule Experience**: Defines the set of emails that a learner
   receives. The two Schedule Experiences are:

   -  “Recurring Nudge and Upgrade Reminder”
   -  “Course Updates”

-  **Upgrade Deadline**: The date before which a learner is encouraged to
   purchase a verified certificate. By default, a Schedule imposes a "soft"
   upgrade deadline (meaning, a suggested, but not final, date) 21 days from
   when a learner enrolled in a course. A self-paced course imposes a "hard"
   upgrade deadline that is the course-wide expiration date for upgrading on the
   course. A Schedule uses whichever date is earlier.

-  **Course Update**: We refer to "Weekly Course Highlight Messages" as "Course
   Updates" in the code. In contexts outside of this app, Course Updates refer
   to bulk-emails manually sent out by course instructors and for the blocks of
   text that course instructors can add to the top of the course outline. We
   plan on removing this term from this app's code to avoid confusion.

-  **Section**: From our
   `documentation <http://edx.readthedocs.io/projects/edx-%20partner-course-staff/en/latest/developing_course/course_sections.html#what-is-a-section>`__,
   “A section is the topmost category in your course. A section can
   represent a time period in your course, a chapter, or another
   organizing principle. A section contains one or more subsections.”
   For the purposes of Weekly Section Highlights Messages, we assume
   that a section contains a week’s worth of learning material.

-  **Weekly Section Highlights**: A list of topics that learners will
   encounter in a section of a course. Course authors enter section
   highlights in the Studio UI.

-  **Resolver**: A Python class that identifies which learners to
   message and sends them emails. We create a Resolver subclass to
   manage each message type. An “UpgradeReminderResolver” sends Upgrade
   Reminder messages, a “RecurringNudgeResolver” sends Recurring Nudge
   messages, and a “CourseUpdateResolver” sends Weekly Section Highlight
   Messages.

-  **MessageType**: A Python class that represents a kind of email
   message. It specifies the Django template the app uses the render the
   email. `MessageType is an ACE
   concept <https://edx-ace.readthedocs.io/en/latest/modules.html#edx_ace.message.MessageType>`__.

-  **Task**: A
   `Celery <http://docs.celeryproject.org/en/latest/index.html>`__
   asynchronous class/function that is run in a separate process from
   the main Django LMS process. In the app, a task is created to email
   groups of learners. We bin learners to distribute the amount of work
   each task performs. We email each bin's worth of learners in a task.

-  **Bin**: In the Schedules app, we divide the learners we are emailing
   into N “bins” (by default, N is 24). We do this to evenly distribute
   the number of emails each task must send.

-  **Email Backend**: An external service that ACE will use to deliver emails.
   For now, ACE only supports `Sailthru <http://www.sailthru.com/>`__ as an
   email backend.


An Overview of edX's Dynamic Pacing System
------------------------------------------

.. image:: img/system_diagram.png


Running the Management Commands
-------------------------------

There are three management commands in the Schedules app. Each command sends a
message type: ``send_recurring_nudge``; ``send_upgrade_reminder``; and
``send_course_update``.

You must specify the Site for which you are sending emails in the command:

::

    ./manage.py lms send_recurring_nudge example.com

Make sure to specify your development environment with the “settings”
flag. For example, if you are running a command in docker devstack, you
can use:

::

    ./manage.py lms --settings devstack_docker send_recurring_nudge example.com

You can override the “current date” when running a command. The app will run,
using the date you specify as its "today":

::

    ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --date 2017-11-13

If the app is paired with Sailthru, you can override which email addresses the
app sends to. The app will send all emails to the address you specify:

::

    ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --override-recipient-email developer@example.com

These management commands are meant to be run daily. We schedule them to
run automatically in a Jenkins job. You can use a similar automation
tool, like “cron”, to schedule a daily run of the app.

Configuring A.C.E.
------------------

These instructions assume you have already setup an Open edX instance or
are running devstack. See the `Open edX Developer’s
Guide <http://edx.readthedocs.io/projects/edx-developer-guide/en/latest/>`__
for information on setting them up.

The Schedule app relies on ACE. When live, ACE sends emails to users
through Sailthru. You can instead configure ACE to write emails
to the local filesystem, which can be useful for debugging.

File Back-end
~~~~~~~~~~~~~

Edit the ``lms/envs/common.py`` or ``lms/envs/private.py``\ and
add/change the following:

.. code:: python

    ACE_CHANNEL_SAILTHRU_DEBUG = True

By default, your devstack should be configured to use the ``file_email``
ACE channel. This ACE channel saves the emails to
``/path/to/your/devstack/src/ace_messages/*.html`` on your host machine
(the host path corresponds to ``/edx/src/ace_messages/`` in your devstack docker
container). To view the emails, open the saved files in your browser.

Sailthru Back-end
~~~~~~~~~~~~~~~~~

To configure ACE to send emails to users’ email addresses, add a
`Sailthru <http://www.sailthru.com/>`__ back-end configuration. See the
`edx-ace
documentation <https://edx-ace.readthedocs.io/en/latest/getting_started.html#sailthruemailchannel-settings>`__
for instructions on setting up a Sailthru API key and secret.

Make sure to add the following settings in either ``lms/envs/common.py``
or ``lms/envs/private.py``:

.. code:: python

    ACE_CHANNEL_SAILTHRU_DEBUG = False
    ACE_ENABLED_CHANNEL = ['sailthru_email']
    ACE_ENABLED_POLICIES = ['bulk_email_optout']
    ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME = '<insert_sailthru_template_name_here>'

Django Settings
---------------

These settings populate links in the emails to external
social media, marketing websites, app stores, etc.

Edit the ``lms/envs/common.py`` or ``lms/envs/private.py`` and
add/change the following:

.. code:: python

    FEATURES = {
        'ENABLE_MKTG_SITE': True,
    }
    MKTG_URLS = {
        'ROOT': '<insert_lms_url_here>',
    }
    SOCIAL_MEDIA_FOOTER_URLS = {
        'tumblr': '<insert_tumblr_url_here>',
        'reddit': '<insert_reddit_url_here>',
        'twitter': '<insert_twitter_url_here>',
        'google_plus': '<insert_google_plus_url_here>',
        'youtube': '<insert_youtube_url_here>',
        'linkedin': '<insert_linkedin_url_here>',
        'meetup': '<insert_meetup_url_here>',
        'facebook': '<insert_facebook_url_here>',
    }
    MOBILE_STORE_URLS = {
        'google': '<insert_play_store_url_here>',
        'apple': '<insert_app_store_url_here>',
    }
    CONTACT_MAILING_ADDRESS = '<insert_physical_address_here>'

Configuration Flags
-------------------

Configuring Schedule Creation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Make sure a Site has been created at ``<lms_url>/admin/sites/site``.

ScheduleConfig
^^^^^^^^^^^^^^

In the Django admin panel at
``<lms_url>/admin/schedules/scheduleconfig/`` create a ScheduleConfig
and link it to the Site. Make sure to enable all of the settings:

-  ``create_schedules``: enables creating new Schedules when new Course
   Enrollments are created.
-  ``hold_back_ratio``: ratio of all new Course Enrollments that should
   NOT have a Schedule created.

Self-paced Configuration
^^^^^^^^^^^^^^^^^^^^^^^^

Schedules will only be created for a course if it is self-paced. A
course can be configured to be self-paced by going to
``<studio_url>/admin/self_paced/selfpacedconfiguration/`` and adding an
enabled self paced config. Then, go to Studio settings for the course
and change the Course Pacing value to “Self-Paced”. Note that the Course
Start Date has to be set to sometime in the future in order to change
the Course Pacing.

Configuring Upgrade Deadline on Schedule
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The upgrade reminder message type depends on there being a date in the
``upgrade_deadline`` field of the Schedule model. Up-sell messaging will
also be added to the recurring nudge and course updates message types
when an upgrade deadline date is present.

DynamicUpgradeDeadlineConfiguration models
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In order to enable filling in the ``upgrade_deadline`` field of new
Schedule models created, you must create and enable one of the
following:

-  A DynamicUpgradeDeadlineConfiguration toggles the feature for all
   courses globally.
-  A OrgDynamicUpgradeDeadlineConfiguration toggles the feature for all
   courses in a particular organization.
-  A CourseDynamicUpgradeDeadlineConfiguration toggles the feature for a
   particular course.

The CourseDynamicUpgradeDeadlineConfiguration takes precedence over the
OrgDynamicUpgradeDeadlineConfiguration which takes precedence over the
global DynamicUpgradeDeadlineConfiguration.

The “deadline days” field specifies how many days from the day of the
learner’s enrollment will be their soft upgrade deadline on the Schedule
model.

Verified Course Mode
^^^^^^^^^^^^^^^^^^^^

The ``upgrade_deadline`` will only be filled for a course if it has a
verified course mode. To add a verified course mode to a course, go to
``<lms_url>/admin/course_modes/coursemode/`` and add a course mode
linked with the course with the "Mode" equal to "verified".

Configuring Email Sending
~~~~~~~~~~~~~~~~~~~~~~~~~

.. scheduleconfig-1:

ScheduleConfig
^^^^^^^^^^^^^^

The ScheduleConfig model at
``<lms_url>/admin/schedules/scheduleconfig/`` also has fields which
configure enqueueing and delivering emails per message type:

-  ``enqueue_*``: allows sending email tasks of this message type to
   celery.
-  ``deliver_*``: allows delivering emails through ACE for this message
   type.

.. roll-out-waffle-flag-1:

Roll-out Waffle Flag
^^^^^^^^^^^^^^^^^^^^

Another roll-out related course waffle flag that we plan to delete
called ``schedules.send_updates_for_course`` will enable sending
specifically the course updates email per-course.

Configuring Highlights UI in Studio
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The button and modal on the course outline page that allows course
authors to enter section highlights can be toggled globally by going to
``<lms_url>/admin/waffle/switch/`` and adding an active switch called
``dynamic_pacing.studio_course_update``.

This is a roll-out related waffle switch that we will eventually delete.

Configuring a Learner’s Schedule
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Emails will only be sent to learners who have Schedule ``start_date``\ s
or ``upgrade_deadline``\ s and ScheduleExperience that match the
criteria for the message type.

Recurring Nudge
^^^^^^^^^^^^^^^

-  Learners must have the ScheduleExperience type of "Recurring Nudge
   and Upgrade Reminder".
-  Their Schedule ``start_date`` must be 3 or 10 days before the current
   date.

Upgrade Reminder
^^^^^^^^^^^^^^^^

-  Learners must have the ScheduleExperience type of “Recurring Nudge
   and Upgrade Reminder”.
-  Their Schedule ``upgrade_deadline`` must be 2 days after the current
   date.

Course Update
^^^^^^^^^^^^^

-  Learners must have the ScheduleExperience type of “Course Updates”.
-  Their Schedule ``start_date`` must be 7, 14, or any increment of 7
   days up to 77 days before the current date.

Analytics
~~~~~~~~~

To track the performance of these communications, there is an integration setup
with Google Analytics and Segment. When a message is sent a Segment event is
emitted that contains the unique message identifier and a bunch of other data
about the message that was sent. When a user opens an email, an invisible
tracking pixel is rendered that records an event in Google Analytics. When a
user clicks a link in the email,
`UTM parameters <https://en.wikipedia.org/wiki/UTM_parameters>`__ are included
in the query string which allow Google Analytics to know that the traffic was
driven to the LMS by that email.

Using these three pieces of information you can track many key metrics.
Specifically: you can monitor the number of messages sent, the ratio of messages
opened to messages sent, and the ratio of links clicked in messages to the
messages opened. These help you answer a few key questions: How many people
am I reaching? How many people are opening my messages? How many people are
persuaded to actually come back to my site after reading my message?

You can also filter Google Analytics to compare the behavior of the users
coming to your platform from these emails relative to other sources of traffic.

Enabling Tracking
^^^^^^^^^^^^^^^^^

-  In either your site configuration or django settings set
   ``GOOGLE_ANALYTICS_TRACKING_ID`` to your Google Analytics tracking ID. This
   will look something like UA-XXXXXXX-X
-  In your django settings set ``LMS_SEGMENT_KEY`` to your Segment project
   write key.

Emitted Events
^^^^^^^^^^^^^^

The segment event that is emitted when a message is sent is named
"edx.bi.email.sent" and contains the following information:

-  ``send_uuid`` uniquely identifies this batch of emails that are being sent to
   many learners.
-  ``uuid`` uniquely identifies this particular message being sent to exactly
   one learner.
-  ``site`` is the site that the email was sent for.
-  ``app_label`` will always be "schedules" for the emails sent from here.
-  ``name`` will be the name of the message that was sent: recurringnudge_day3,
   recurringnudge_day10, upgradereminder, or courseupdate.
- ``primary_course_id`` identifies the primary course discussed in the email if
  the email was sent on behalf of several courses.
- ``language`` is the language the email was translated into.
- ``course_ids`` is a list of all courses that this email was sent on behalf of.
  This can be truncated if the list of courses is long.
- ``num_courses`` is the actual number of courses covered by this message. This
  may differ from the course_ids list if the list was truncated.

The Google Analytics event that is emitted when a learner opens an email has
the following properties:

-  ``action`` is "edx.bi.email.opened"
-  ``category`` is "email"
-  ``label`` is the primary_course_id described above
-  ``campaign source`` is "schedules"
-  ``campaign medium`` is "email"
-  ``campaign content`` is the unique identifier for the message

When the user clicks a link in the email the following UTM parameters are
included in the URL:

-  ``campaign source`` is "schedules"
-  ``campaign medium`` is "email"
-  ``campaign content`` is the unique identifier for the message
-  ``campaign term`` is the primary_course_id described above

Litmus
------

When designing email templates, it is important to test the rendered emails in a
variety of email clients to ensure that they render correctly. EdX uses a tool
called `Litmus <http://litmus.com/>`__ for this process.

To begin using Litmus, follow these steps:

1. Make sure that ACE is configured to use Sailthru (see instructions above).
2. Go to the `Litmus checklist page <https://litmus.com/checklist>`__ and start
   a new checklist.
3. The checklist will provide you with an email address to which you will send 
   a test email.
4. Send an email. Use one of the management commands with the 
   `--override-recipient-email` flag. Use the Litmus email you got in step 3 
   as the flag value.

::

    ./manage.py lms --settings devstack_docker send_recurring_nudge example.com --override-recipient-email PUT-LITMUS-ADDRESS-HERE

Using the Litmus Browser Extenstion to test emails saved as local files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. Configure your devstack to use the "file_email" channel for ACE (see
   instructions above).
2. Install the Litmus `chrome browser extension
   <https://chrome.google.com/webstore/detail/litmus/makmhllelncgkglnpaipelogkekggpio>`__.
3. Send an email by running the management command. This should save the email
   to a file.
4. Open the saved file in chrome on your host. It should be in
   `/path/to/your/devstack/src/ace_messages/*.html`.
5. Open the Litmus extension.
6. When you regenerate emails, you can easily refresh the previews in Litmus.
