From: Michael Tremer Date: Tue, 19 Nov 2019 13:46:07 +0000 (+0000) Subject: Implement drip campaigns X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d73bba549627ef488ed77bf3f7db01e5cee00fe2;p=ipfire.org.git Implement drip campaigns When users sign up, they will now receive a number of emails to help them setting up their accounts and getting involved with IPFire. Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index a098280d..29b8a516 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,6 +51,7 @@ backend_PYTHON = \ src/backend/accounts.py \ src/backend/base.py \ src/backend/blog.py \ + src/backend/campaigns.py \ src/backend/countries.py \ src/backend/database.py \ src/backend/decorators.py \ @@ -123,7 +124,10 @@ templates_auth_DATA = \ templates_authdir = $(templatesdir)/auth templates_auth_messages_DATA = \ + src/templates/auth/messages/donation-reminder.txt \ src/templates/auth/messages/password-reset.txt \ + src/templates/auth/messages/profile-setup.txt \ + src/templates/auth/messages/profile-setup-2.txt \ src/templates/auth/messages/register.txt templates_auth_messagesdir = $(templates_authdir)/messages diff --git a/src/backend/accounts.py b/src/backend/accounts.py index 46a5061b..6d01ac2e 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -374,6 +374,9 @@ class Accounts(Object): self.backend.messages.send_template("people/messages/new-account", recipients=["moderators@ipfire.org"], account=account) + # Launch all drip campaigns + self.backend.campaigns.launch(account) + return account def create(self, uid, email, first_name, last_name, country_code=None): @@ -799,6 +802,10 @@ class Account(LDAPObject): def email(self): return self._get_string("mail") + @property + def email_to(self): + return "%s <%s>" % (self, self.email) + # Mail Routing Address def get_mail_routing_address(self): diff --git a/src/backend/base.py b/src/backend/base.py index f85d2ad8..80c50998 100644 --- a/src/backend/base.py +++ b/src/backend/base.py @@ -6,6 +6,7 @@ import tornado.httpclient from . import accounts from . import blog +from . import campaigns from . import database from . import geoip from . import fireinfo @@ -98,6 +99,8 @@ class Backend(object): "check-mirrors" : self.mirrors.check_all, "check-spam" : self.accounts.check_spam, "cleanup" : self.cleanup, + "launch-campaigns" : self.campaigns.launch_manually, + "run-campaigns" : self.campaigns.run, "scan-files" : self.releases.scan_files, "send-all-messages" : self.messages.queue.send_all, "test-blacklist" : self.geoip.test_blacklist, @@ -119,6 +122,10 @@ class Backend(object): if r: raise SystemExit(r) + @lazy_property + def campaigns(self): + return campaigns.Campaigns(self) + @lazy_property def groups(self): return accounts.Groups(self) diff --git a/src/backend/campaigns.py b/src/backend/campaigns.py new file mode 100644 index 00000000..746870e3 --- /dev/null +++ b/src/backend/campaigns.py @@ -0,0 +1,90 @@ +#!/usr/bin/python3 + +import logging + +from .decorators import * +from .misc import Object + +class Campaigns(Object): + async def launch_manually(self, uid): + account = self.backend.accounts.get_by_uid(uid) + if account: + self.launch(account) + + def launch(self, account): + logging.debug("Launching all campaigns for %s" % account) + + # Update old timestamps first + self.db.execute("UPDATE campaign_templates \ + SET launch_at = launch_at + repeat_after \ + WHERE (launch_at IS NOT NULL AND launch_at <= CURRENT_TIMESTAMP) \ + AND repeat_after IS NOT NULL") + + # Launch all campaigns + self.db.execute("INSERT INTO campaign_emails(account_uid, template, \ + launch_at, repeat_after, groups ) \ + SELECT %s, template, COALESCE(launch_at, CURRENT_TIMESTAMP + launch_after), \ + repeat_after, groups FROM campaign_templates", account.uid) + + def _get_campaign_emails(self, query, *args): + res = self.db.query(query, *args) + + for row in res: + yield CampaignEmail(self.backend, row.id, data=row) + + async def run(self): + with self.db.transaction(): + emails = self._get_campaign_emails("SELECT * FROM campaign_emails \ + WHERE launch_at <= CURRENT_TIMESTAMP ORDER BY launch_at") + + # Send them all + for email in emails: + email.send() + + +class CampaignEmail(Object): + def init(self, id, data=None): + self.id = id + self.data = data + + @lazy_property + def account(self): + return self.backend.accounts.get_by_uid(self.data.account_uid) + + @property + def template(self): + return self.data.template + + @property + def repeat_after(self): + return self.data.repeat_after + + def send(self): + # Delete if the account does not exist any more + if not self.account: + return self._delete() + + logging.info("Sending %s to %s" % (self.template, self.account)) + + # Generate the email + self.backend.messages.send_template(self.template, account=self.account) + + # Update this email for the next launch + self._update() + + def _update(self): + # If this email repeats, we will update the timestamp + if self.repeat_after: + self.db.execute("UPDATE campaign_emails \ + SET launch_at = launch_at + repeat_after \ + WHERE id = %s", self.id) + + # Otherwise we will delete it + else: + self._delete() + + def _delete(self): + """ + Deletes this email + """ + self.db.execute("DELETE FROM campaign_emails WHERE id = %s", self.id) diff --git a/src/backend/messages.py b/src/backend/messages.py index 1b0e85a2..28b717c0 100644 --- a/src/backend/messages.py +++ b/src/backend/messages.py @@ -6,6 +6,7 @@ import email.mime.nonmultipart import email.utils import logging import subprocess +import tornado.locale import tornado.template from . import accounts diff --git a/src/crontab/ipfire b/src/crontab/ipfire index eb3e52b9..df1293c6 100644 --- a/src/crontab/ipfire +++ b/src/crontab/ipfire @@ -9,6 +9,9 @@ SHELL=/bin/bash # Send messages * * * * * nobody ipfire.org send-all-messages +# Run campaigns +*/5 * * * * nobody ipfire.org run-campaigns + # Cleanup once an hour 30 * * * * nobody ipfire.org cleanup diff --git a/src/templates/auth/messages/donation-reminder.txt b/src/templates/auth/messages/donation-reminder.txt new file mode 100644 index 00000000..2f066268 --- /dev/null +++ b/src/templates/auth/messages/donation-reminder.txt @@ -0,0 +1,28 @@ +From: Michael Tremer from the IPFire Project +To: {{ account.email_to }} +Subject: {{ _("Please help us with your donation!") }} + +{{ _("Hey again, %s,") % account.first_name }} + +{{ _("IPFire runs on supporters' donations, people like you!") }} + +{{ _("Why do we need you donations?") }} + +* {{ _("Your money ensures the longevity and long-term success of this project.") }} +* {{ _("It helps us fund developers and extend our skills") }} +* {{ _("It will aid us to promote IPFire to more people around the world") }} +* {{ _("This funds conferences, where we focus on future projects") }} +* {{ _("It pays for our hosting") }} + +{{ _("All this, as you would understand, requires money. Every single donation counts.") }} + +{{ _("If you want to see IPFire thrive, we need your support.") }} + +{{ _("The best way to do this is by setting up a monthly donation which you can do here:") }} + + https://www.ipfire.org/donate?frequency=monthly&amount=10 + +{{ _("We also have other ways to donate. Please go to https://www.ipfire.org/donate for details.") }} + +{{ _("Thank you so much for your support,") }} +{{ _("-Michael")}} diff --git a/src/templates/auth/messages/profile-setup-2.txt b/src/templates/auth/messages/profile-setup-2.txt new file mode 100644 index 00000000..0a9c4ae2 --- /dev/null +++ b/src/templates/auth/messages/profile-setup-2.txt @@ -0,0 +1,17 @@ +From: Arne Fitzenreiter from the IPFire Project +To: {{ account.email_to }} +Subject: {{ _("Hi!") }} + +{{ _("Hello once again, %s,") % account.first_name }} + +{{ _("we hope you are enjoying using IPFire.") }} + +{{ _("Did you know that you can get help from our community at https://community.ipfire.org?") }} +{{ _("People like me often post on here, providing help and support.") }} + +{{ _("But we also rely on you donations. Please consider helping us by setting up a small monthly donation:") }} + + https://www.ipfire.org/donate?frequency=monthly + +{{ _("Thank you, we really appreciate your support,") }} +{{ _("-Arne")}} diff --git a/src/templates/auth/messages/profile-setup.txt b/src/templates/auth/messages/profile-setup.txt new file mode 100644 index 00000000..57963388 --- /dev/null +++ b/src/templates/auth/messages/profile-setup.txt @@ -0,0 +1,19 @@ +From: Michael Tremer from the IPFire Project +To: {{ account.email_to }} +Subject: {{ _("Welcome to the IPFire Project!") }} + +{{ _("Hello %s!") % account.first_name }} + +{{ _("I would like to introduce myself: I'm Michael and I am one of the people behind the project. We are a team of passionate people who try to make the Internet a better place. On behalf of everyone, I would like to say: Welcome to the IPFire Project!") }} + +{{ _("We want you to feel a part of our team. Can I ask you to set up your profile? We would love to know a little bit more about you.") }} +{{ _("To do this, please log on to your profile and click the edit button.") }} + +{{ _("I would also like to invite you to join our community at https://community.ipfire.org, if you haven't already done so.") }} + +{{ _("Finally, this organisation relies on the generous donations of people like you. If you can, please consider supporting this project and the team behind it, so that we can continue our long-term vision, fund developers and promote our project.") }} + +{{ _("You can do this at https://www.ipfire.org/donate.") }} + +{{ _("Thank you,") }} +{{ _("-Michael") }} diff --git a/src/templates/auth/messages/register.txt b/src/templates/auth/messages/register.txt index e1adf20c..4d2a02d0 100644 --- a/src/templates/auth/messages/register.txt +++ b/src/templates/auth/messages/register.txt @@ -1,6 +1,6 @@ From: IPFire Project To: {{ first_name }} {{ last_name }} <{{ email }}> -Subject: {{ _("Welcome to the IPFire Project!") }} +Subject: {{ _("Please activate your account for the IPFire Project") }} {{ _("Hello %s!") % first_name }}