2020-06-04 04:34:00 +00:00
.. image :: docs/birdsong.svg
:width: 400
:alt: Birdsong Logo
2020-05-08 02:38:21 +00:00
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/> `_ .
2023-03-15 00:54:58 +00:00
.. image :: docs/birdsong-admin-menu.png
:width: 379
:alt: Birdsong Admin Menu
2022-08-24 01:21:06 +00:00
2020-05-08 02:38:21 +00:00
Basic usage
===========
2023-03-15 03:44:55 +00:00
Install birdsong:
2020-05-08 02:38:21 +00:00
.. code-block :: shell
pip install wagtail-birdsong
2023-03-15 03:24:35 +00:00
Add the following to your `` INSTALLED_APPS `` :
2020-05-08 02:38:21 +00:00
.. code-block :: python
2023-03-15 03:24:35 +00:00
INSTALLED_APPS = [
...
'mjml',
'birdsong',
'wagtail.contrib.modeladmin',
...
]
2020-05-08 02:38:21 +00:00
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.
2023-03-15 03:24:35 +00:00
`` models.py ``
2020-05-08 02:38:21 +00:00
.. code-block :: python
from birdsong.blocks import DefaultBlocks
2020-05-26 00:23:37 +00:00
from birdsong.models import Campaign
2020-05-08 02:38:21 +00:00
from django.db import models
from wagtail.admin.edit_handlers import StreamFieldPanel
from wagtail.core.fields import StreamField
2023-03-15 00:54:58 +00:00
class SaleCampaign(Campaign):
2020-05-08 02:38:21 +00:00
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.
2023-03-15 03:44:55 +00:00
**NOTE:** The `` CampaignAdmin `` is just an extension of Wagtail's `` ModelAdmin `` class so most of the same options are available for overriding functionality.
**NOTE:** `` BirdsongAdminGroup `` can be disabled with `` BIRDSONG_ADMIN_GROUP `` setting if you want to `` modeladmin_register `` your `` CampaignAdmin `` directly.
2023-03-15 03:24:35 +00:00
`` wagtail_hooks.py ``
2020-05-08 02:38:21 +00:00
.. code-block :: python
2023-03-15 00:54:58 +00:00
from birdsong.wagtail_hooks import (
CampaignAdmin, ContactAdmin, BirdsongAdminGroup, modeladmin_re_register
)
from .models import SaleCampaign
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
class CampaignAdmin(CampaignAdmin):
campaign = SaleCampaign
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
@modeladmin_re_register
class BirdsongAdminGroup(BirdsongAdminGroup):
items = (CampaignAdmin, ContactAdmin)
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
Create your campaign template in `` {app_folder}/templates/mail/{model_name}.html `` e.g. `` email/templates/mail/sale_campaign.html `` ,
2020-05-08 02:38:21 +00:00
alternatively override the `` get_template `` method on your campaign model.
2023-03-15 03:44:55 +00:00
**NOTE:** 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.
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
`` sale_campaign.html ``
2020-05-08 02:38:21 +00:00
.. 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 %}
2020-06-16 00:19:37 +00:00
You're now ready to go!
.. image :: docs/birdsong-preview.png
:width: 900
2023-03-15 00:54:58 +00:00
:alt: Birdsong Preview
2020-06-16 00:19:37 +00:00
2023-03-15 00:54:58 +00:00
Custom Contact model
2020-05-08 02:38:21 +00:00
=====================
2020-06-16 10:42:59 +00:00
By default the included `` Contact `` model is used for every campaign, but you may want to store extra data, like names and preferences.
2020-05-08 02:38:21 +00:00
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
2023-03-15 00:54:58 +00:00
from birdsong.wagtail_hooks import (
CampaignAdmin, ContactAdmin, BirdsongAdminGroup, modeladmin_re_register
)
from .models import SaleCampaign, ExtendedContact # NOTE: Import your custom Contact model
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
class CampaignAdmin(CampaignAdmin):
campaign = SaleCampaign
contact_class = ExtendedContact # NOTE: Teach CampaignAdmin to use your custom Contact model
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
class ContactAdmin(ContactAdmin): # NOTE: Overload ContactAdmin to list/edit/add your Contacts
2020-05-08 02:38:21 +00:00
model = ExtendedContact
list_diplay = ('email', 'first_name', 'last_name', 'location')
2023-03-15 00:54:58 +00:00
@modeladmin_re_register
class BirdsongAdminGroup(BirdsongAdminGroup):
items = (CampaignAdmin, ContactAdmin)
2020-05-08 02:38:21 +00:00
2023-02-01 03:25:59 +00:00
`` base.py ``
.. code-block :: python
2023-03-15 13:45:35 +00:00
# You may want to redefine the test contact (used in previews) with your new ExtendedContact fields
2023-02-01 03:25:59 +00:00
BIRDSONG_TEST_CONTACT = {
2023-03-15 00:54:58 +00:00
'first_name': 'Wagtail', # new ExtendedContact field
'last_name': 'Birdsong', # new ExtendedContact field
'email': 'birdsong@example.com',
'location': 'us', # new ExtendedContact field
2023-02-01 03:25:59 +00:00
}
2023-03-15 00:54:58 +00:00
2020-05-08 02:38:21 +00:00
Filtering on contact properties
===============================
2022-06-23 09:13:18 +00:00
You might want to only send a campaign to a subset of your `` Contact `` models. Creating a filter using `django-filter <https://django-filter.readthedocs.io/en/main/> `_ and adding it to the `` CampaignAdmin `` allows users to filter on any property.
2020-05-08 02:38:21 +00:00
`` 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
2023-03-15 00:54:58 +00:00
from birdsong.wagtail_hooks import (
CampaignAdmin, ContactAdmin, BirdsongAdminGroup, modeladmin_re_register
)
from .models import SaleCampaign, ExtendedContact
from .filters import ContactFilter # NOTE: Import your custom Contact filter
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
class CampaignAdmin(CampaignAdmin):
campaign = SaleCampaign
contact_class = ExtendedContact
contact_filter_class = ContactFilter # NOTE: Use your custom Contact filter
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
class ContactAdmin(ContactAdmin):
model = ExtendedContact
list_diplay = ('email', 'first_name', 'last_name', 'location')
2020-05-08 02:38:21 +00:00
2023-03-15 00:54:58 +00:00
@modeladmin_re_register
class BirdsongAdminGroup(BirdsongAdminGroup):
items = (CampaignAdmin, ContactAdmin)
Users will now be able to send campaigns to a subset of contacts based on location.
2020-05-08 02:38:21 +00:00
Unsubscribe url
===============
2020-06-04 04:41:47 +00:00
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.
2020-05-08 02:38:21 +00:00
`` urls.py ``
.. code-block :: python
from birdsong import urls as birdsong_urls
from django.urls import include, path
urlpatterns = [
...
path('mail/', include(birdsong_urls)),
...
]
2023-03-15 00:54:58 +00:00
`` sale_campaign.html ``
2020-05-08 02:38:21 +00:00
.. 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>
2022-07-06 15:41:40 +00:00
<mj-column>
<mj-text align="center">
Click <a href="{{ site.full_url }}{% url 'birdsong:unsubscribe' contact.id %}">here</a> to unsubscribe.
</mj-text>
</mj-column>
2020-05-08 02:38:21 +00:00
</mj-section>
{% endblock email_body %}
2023-03-15 00:54:58 +00:00
Future features
===============
2020-05-08 02:38:21 +00:00
2020-06-04 04:34:00 +00:00
- More tests!
2020-06-16 00:23:16 +00:00
- Proper docs
2020-05-08 02:38:21 +00:00
- Backends other thans SMTP for sending emails so analytics can be gathered (email opened, bounced etc)
2020-06-04 04:34:00 +00:00
- Reloading the preview on edit
2020-06-16 10:42:59 +00:00
- Broader permissions for campaigns (send, preview, test send)