Create, send, preview, edit and test email campaigns from within Wagtail
Go to file
Seb d63e24adc5 Actually use postgres 2020-06-15 09:04:53 +10:00
birdsong Test fixes 2020-06-12 16:15:25 +10:00
docs logo path 2020-06-04 14:36:13 +10:00
tests Actually use postgres 2020-06-15 09:04:53 +10:00
.dockerignore Switch to docker so can use mjml easily 2020-05-29 09:56:01 +10:00
.gitignore Switch to docker so can use mjml easily 2020-05-29 09:56:01 +10:00
.gitlab-ci.yml Actually use postgres 2020-06-15 09:04:53 +10:00
Dockerfile.dev Make template for test app, start some new tests 2020-05-29 12:34:53 +10:00
MANIFEST.in Misse some 2020-05-01 10:56:39 +10:00
README.rst Update readme again, up version 2020-06-04 14:41:47 +10:00
docker-compose.yml Copying + smtp dev server 2020-06-04 11:11:51 +10:00
runtests.py Basic test cases 2020-05-29 11:56:25 +10:00
settings_dev.py Improvements on the inspect view 2020-06-04 14:07:35 +10:00
setup.py Add thing to fix wheel 2020-05-28 14:40:25 +10:00

README.rst

.. image:: docs/birdsong.svg
    :width: 400
    :alt: Birdsong Logo

A plugin for wagtail that allows you to create, send, preview, edit and test email campaigns from within Wagtail.
Campaign templates are created using `mjml <https://mjml.io/>`_.

Basic usage
===========

Install birdsong:

.. code-block:: shell
    
    pip install wagtail-birdsong


Add the following to your installed apps:

.. code-block:: python

    'mjml',
    'birdsong',
    'wagtail.contrib.modeladmin',


Make a new app e.g. ``email``, create a ``models.py`` with a model that extends the included ``Campaign`` model. Some compatible mjml streamfield blocks are included in birdsong for convenience.

.. code-block:: python

    from birdsong.blocks import DefaultBlocks
    from birdsong.models import Campaign
    from django.db import models
    from wagtail.admin.edit_handlers import StreamFieldPanel
    from wagtail.core.fields import StreamField


    class SaleEmail(Campaign):
        body = StreamField(DefaultBlocks())

        panels = Campaign.panels + [
            StreamFieldPanel('body'),
        ]

Then in the same app, create a ``wagtail_hooks.py`` if it doesn't exist, this is where the admin is created
for content editors to create/edit/send campaigns.

The ``CampaignAdmin`` is just an extension of Wagtail's ``ModelAdmin`` class so most of the same options are available for overriding functionality.

.. code-block:: python

    from wagtail.contrib.modeladmin.options import modeladmin_register
    from birdsong.options import CampaignAdmin

    from .models import SaleEmail


    @modeladmin_register
    class SaleEmailAdmin(CampaignAdmin):
        campaign = SaleEmail
        menu_label = 'Sale Email'
        menu_icon = 'mail'
        menu_order = 200


Create your campaign template in ``{app_folder}/templates/mail/{model_name}.html`` e.g. ``email/templates/mail/sale_email.html``,
alternatively override the ``get_template`` method on your campaign model.

Campaign templates use django-mjml for responsive, well designed emails. To read up how to setup django-mjml you can read the docs `here <https://github.com/liminspace/django-mjml>`_. There is a base template included in Birdsong that can be extended.

``sale_email.html``

.. code-block:: html

    {% extends "birdsong/mail/base_email.html" %}

    {% block email_body %}
    <mj-section>
        <mj-column>
            <mj-text>Hello {{ contact.email }}!</mj-text>
            {% for b in self.body %}
                {{ b }}
            {% endfor %}
        </mj-column>
    </mj-section>
    {% endblock email_body %}


Custom Contact models
=====================

By default the included ``Contact`` model is used for every campaign, sometimes you may want to store extra data, like names and preferences. 
You can override the default ``Contact`` model by setting an option on the admin for your campaign:

``models.py``

.. code-block:: python

    from birdsong.models import Contact
    from django.db import models

    class ExtendedContact(Contact):
        first_name = models.CharField(max_length=255)
        last_name = models.CharField(max_length=255)
        location = models.CharField(max_length=255)


``wagtail_hooks.py``

.. code-block:: python

    from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
    from birdsong.options import CampaignAdmin

    from .models import ExtendedContact, SaleEmail


    @modeladmin_register
    class SaleEmailAdmin(CampaignAdmin):
        campaign = SaleEmail
        menu_label = 'Sale Email'
        menu_icon = 'mail'
        menu_order = 200
        contact_class = ExtendedContact


    # You may want to add your own modeladmin here to list/edit/add contacts
    @modeladmin_register
    class ContactAdmin(ModelAdmin):
        model = ExtendedContact
        menu_label = 'Contacts'
        menu_icon = 'user'
        list_diplay = ('email', 'first_name', 'last_name', 'location')


Filtering on contact properties
===============================

You might want to only send a campaign to a subset of you ``Contact`` models. Creating a filter using `django-filter <https://django-filter.readthedocs.io/en/master/>`_ and adding it to the ``CampaignAdmin`` allows users to filter on any property.

``filters.py``

.. code-block:: python

    from django_filters import FilterSet
    from django_filters.filters import AllValuesFilter

    from .models import ExtendedContact


    class ContactFilter(FilterSet):
        location = AllValuesFilter()

        class Meta:
            model = ExtendedContact
            fields = ('location',)


``wagtail_hooks.py``

.. code-block:: python

    from wagtail.contrib.modeladmin.options import modeladmin_register
    from birdsong.options import CampaignAdmin

    from .filters import ContactFilter
    from .models import ExtendedContact, SaleEmail


    @modeladmin_register
    class SaleEmailAdmin(CampaignAdmin):
        campaign = SaleEmail
        menu_label = 'Sale Email'
        menu_icon = 'mail'
        menu_order = 200
        contact_class = ExtendedContact
        contact_filter_class = ContactFilter


Users will now be able to send campaigns to a subset of contacts base on location.

Unsubscribe url
===============

Included in birdsong is a basic way for contacts to unsubscribe, just include the url configuration and add the unsubscribe url to your email template.

``urls.py``

.. code-block:: python

    from birdsong import urls as birdsong_urls
    from django.urls import include, path

    urlpatterns = [
        ...
        path('mail/', include(birdsong_urls)),
        ...
    ]

``sale_email.html``

.. code-block:: html

    {% extends "birdsong/mail/base_email.html" %}

    {% block email_body %}
    <mj-section>
        <mj-column>
            <mj-text>Hello {{ contact.email }}!</mj-text>
            {% for b in self.body %}
                {{ b }}
            {% endfor %}
        </mj-column>
    </mj-section>
    <mj-section>
        Click <a href="{{ site.full_url }}{% url 'birdsong:unsubscribe' contact.id %}">here</a> to unsubscribe.
    </mj-section>
    {% endblock email_body %}




Future features:
----------------

- More tests!
- Backends other thans SMTP for sending emails so analytics can be gathered (email opened, bounced etc)
- Reloading the preview on edit
- Broader permissions for campaigns (send, preview, test send)