From 271ead133b55390fa8ff6f12c70493ce9d5354dc Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 2 Feb 2020 21:49:22 +0700 Subject: [PATCH 01/14] organize emails into transactional/ and com/ --- app/email_utils.py | 40 +++++++++---------- templates/emails/{ => com}/new-app.html | 0 templates/emails/{ => com}/new-app.txt | 0 templates/emails/{ => com}/welcome.html | 0 templates/emails/{ => com}/welcome.txt | 0 .../{ => transactional}/activation.html | 0 .../emails/{ => transactional}/activation.txt | 0 .../cannot-create-alias-directory.html | 0 .../cannot-create-alias-directory.txt | 0 .../cannot-create-alias-domain.html | 0 .../cannot-create-alias-domain.txt | 0 .../{ => transactional}/change-email.html | 0 .../{ => transactional}/change-email.txt | 0 .../reply-must-use-personal-email.html | 0 .../reply-must-use-personal-email.txt | 0 .../{ => transactional}/reset-password.html | 0 .../{ => transactional}/reset-password.txt | 0 .../{ => transactional}/test-email.html | 0 .../emails/{ => transactional}/test-email.txt | 0 .../emails/{ => transactional}/trial-end.html | 0 .../emails/{ => transactional}/trial-end.txt | 0 21 files changed, 20 insertions(+), 20 deletions(-) rename templates/emails/{ => com}/new-app.html (100%) rename templates/emails/{ => com}/new-app.txt (100%) rename templates/emails/{ => com}/welcome.html (100%) rename templates/emails/{ => com}/welcome.txt (100%) rename templates/emails/{ => transactional}/activation.html (100%) rename templates/emails/{ => transactional}/activation.txt (100%) rename templates/emails/{ => transactional}/cannot-create-alias-directory.html (100%) rename templates/emails/{ => transactional}/cannot-create-alias-directory.txt (100%) rename templates/emails/{ => transactional}/cannot-create-alias-domain.html (100%) rename templates/emails/{ => transactional}/cannot-create-alias-domain.txt (100%) rename templates/emails/{ => transactional}/change-email.html (100%) rename templates/emails/{ => transactional}/change-email.txt (100%) rename templates/emails/{ => transactional}/reply-must-use-personal-email.html (100%) rename templates/emails/{ => transactional}/reply-must-use-personal-email.txt (100%) rename templates/emails/{ => transactional}/reset-password.html (100%) rename templates/emails/{ => transactional}/reset-password.txt (100%) rename templates/emails/{ => transactional}/test-email.html (100%) rename templates/emails/{ => transactional}/test-email.txt (100%) rename templates/emails/{ => transactional}/trial-end.html (100%) rename templates/emails/{ => transactional}/trial-end.txt (100%) diff --git a/app/email_utils.py b/app/email_utils.py index c6c8fcc4..0bb88150 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -33,8 +33,8 @@ def send_welcome_email(user): send_email( user.email, f"Welcome to SimpleLogin {user.name}", - _render("welcome.txt", name=user.name, user=user), - _render("welcome.html", name=user.name, user=user), + _render("com/welcome.txt", name=user.name, user=user), + _render("com/welcome.html", name=user.name, user=user), ) @@ -42,8 +42,8 @@ def send_trial_end_soon_email(user): send_email( user.email, f"Your trial will end soon {user.name}", - _render("trial-end.txt", name=user.name, user=user), - _render("trial-end.html", name=user.name, user=user), + _render("transactional/trial-end.txt", name=user.name, user=user), + _render("transactional/trial-end.html", name=user.name, user=user), ) @@ -52,10 +52,10 @@ def send_activation_email(email, name, activation_link): email, f"Just one more step to join SimpleLogin {name}", _render( - "activation.txt", name=name, activation_link=activation_link, email=email + "transactional/activation.txt", name=name, activation_link=activation_link, email=email ), _render( - "activation.html", name=name, activation_link=activation_link, email=email + "transactional/activation.html", name=name, activation_link=activation_link, email=email ), ) @@ -65,10 +65,10 @@ def send_reset_password_email(email, name, reset_password_link): email, f"Reset your password on SimpleLogin", _render( - "reset-password.txt", name=name, reset_password_link=reset_password_link + "transactional/reset-password.txt", name=name, reset_password_link=reset_password_link ), _render( - "reset-password.html", name=name, reset_password_link=reset_password_link + "transactional/reset-password.html", name=name, reset_password_link=reset_password_link ), ) @@ -78,14 +78,14 @@ def send_change_email(new_email, current_email, name, link): new_email, f"Confirm email update on SimpleLogin", _render( - "change-email.txt", + "transactional/change-email.txt", name=name, link=link, new_email=new_email, current_email=current_email, ), _render( - "change-email.html", + "transactional/change-email.html", name=name, link=link, new_email=new_email, @@ -98,8 +98,8 @@ def send_new_app_email(email, name): send_email( email, f"Any question/feedback for SimpleLogin {name}?", - _render("new-app.txt", name=name), - _render("new-app.html", name=name), + _render("com/new-app.txt", name=name), + _render("com/new-app.html", name=name), ) @@ -107,8 +107,8 @@ def send_test_email_alias(email, name): send_email( email, f"This email is sent to {email}", - _render("test-email.txt", name=name, alias=email), - _render("test-email.html", name=name, alias=email), + _render("transactional/test-email.txt", name=name, alias=email), + _render("transactional/test-email.html", name=name, alias=email), ) @@ -120,13 +120,13 @@ def send_cannot_create_directory_alias(user, alias, directory): user.email, f"Alias {alias} cannot be created", _render( - "cannot-create-alias-directory.txt", + "transactional/cannot-create-alias-directory.txt", name=user.name, alias=alias, directory=directory, ), _render( - "cannot-create-alias-directory.html", + "transactional/cannot-create-alias-directory.html", name=user.name, alias=alias, directory=directory, @@ -142,10 +142,10 @@ def send_cannot_create_domain_alias(user, alias, domain): user.email, f"Alias {alias} cannot be created", _render( - "cannot-create-alias-domain.txt", name=user.name, alias=alias, domain=domain + "transactional/cannot-create-alias-domain.txt", name=user.name, alias=alias, domain=domain ), _render( - "cannot-create-alias-domain.html", + "transactional/cannot-create-alias-domain.html", name=user.name, alias=alias, domain=domain, @@ -162,14 +162,14 @@ def send_reply_alias_must_use_personal_email(user, alias, sender): user.email, f"Reply from your alias {alias} only works with your personal email", _render( - "reply-must-use-personal-email.txt", + "transactional/reply-must-use-personal-email.txt", name=user.name, alias=alias, sender=sender, user_email=user.email, ), _render( - "reply-must-use-personal-email.html", + "transactional/reply-must-use-personal-email.html", name=user.name, alias=alias, sender=sender, diff --git a/templates/emails/new-app.html b/templates/emails/com/new-app.html similarity index 100% rename from templates/emails/new-app.html rename to templates/emails/com/new-app.html diff --git a/templates/emails/new-app.txt b/templates/emails/com/new-app.txt similarity index 100% rename from templates/emails/new-app.txt rename to templates/emails/com/new-app.txt diff --git a/templates/emails/welcome.html b/templates/emails/com/welcome.html similarity index 100% rename from templates/emails/welcome.html rename to templates/emails/com/welcome.html diff --git a/templates/emails/welcome.txt b/templates/emails/com/welcome.txt similarity index 100% rename from templates/emails/welcome.txt rename to templates/emails/com/welcome.txt diff --git a/templates/emails/activation.html b/templates/emails/transactional/activation.html similarity index 100% rename from templates/emails/activation.html rename to templates/emails/transactional/activation.html diff --git a/templates/emails/activation.txt b/templates/emails/transactional/activation.txt similarity index 100% rename from templates/emails/activation.txt rename to templates/emails/transactional/activation.txt diff --git a/templates/emails/cannot-create-alias-directory.html b/templates/emails/transactional/cannot-create-alias-directory.html similarity index 100% rename from templates/emails/cannot-create-alias-directory.html rename to templates/emails/transactional/cannot-create-alias-directory.html diff --git a/templates/emails/cannot-create-alias-directory.txt b/templates/emails/transactional/cannot-create-alias-directory.txt similarity index 100% rename from templates/emails/cannot-create-alias-directory.txt rename to templates/emails/transactional/cannot-create-alias-directory.txt diff --git a/templates/emails/cannot-create-alias-domain.html b/templates/emails/transactional/cannot-create-alias-domain.html similarity index 100% rename from templates/emails/cannot-create-alias-domain.html rename to templates/emails/transactional/cannot-create-alias-domain.html diff --git a/templates/emails/cannot-create-alias-domain.txt b/templates/emails/transactional/cannot-create-alias-domain.txt similarity index 100% rename from templates/emails/cannot-create-alias-domain.txt rename to templates/emails/transactional/cannot-create-alias-domain.txt diff --git a/templates/emails/change-email.html b/templates/emails/transactional/change-email.html similarity index 100% rename from templates/emails/change-email.html rename to templates/emails/transactional/change-email.html diff --git a/templates/emails/change-email.txt b/templates/emails/transactional/change-email.txt similarity index 100% rename from templates/emails/change-email.txt rename to templates/emails/transactional/change-email.txt diff --git a/templates/emails/reply-must-use-personal-email.html b/templates/emails/transactional/reply-must-use-personal-email.html similarity index 100% rename from templates/emails/reply-must-use-personal-email.html rename to templates/emails/transactional/reply-must-use-personal-email.html diff --git a/templates/emails/reply-must-use-personal-email.txt b/templates/emails/transactional/reply-must-use-personal-email.txt similarity index 100% rename from templates/emails/reply-must-use-personal-email.txt rename to templates/emails/transactional/reply-must-use-personal-email.txt diff --git a/templates/emails/reset-password.html b/templates/emails/transactional/reset-password.html similarity index 100% rename from templates/emails/reset-password.html rename to templates/emails/transactional/reset-password.html diff --git a/templates/emails/reset-password.txt b/templates/emails/transactional/reset-password.txt similarity index 100% rename from templates/emails/reset-password.txt rename to templates/emails/transactional/reset-password.txt diff --git a/templates/emails/test-email.html b/templates/emails/transactional/test-email.html similarity index 100% rename from templates/emails/test-email.html rename to templates/emails/transactional/test-email.html diff --git a/templates/emails/test-email.txt b/templates/emails/transactional/test-email.txt similarity index 100% rename from templates/emails/test-email.txt rename to templates/emails/transactional/test-email.txt diff --git a/templates/emails/trial-end.html b/templates/emails/transactional/trial-end.html similarity index 100% rename from templates/emails/trial-end.html rename to templates/emails/transactional/trial-end.html diff --git a/templates/emails/trial-end.txt b/templates/emails/transactional/trial-end.txt similarity index 100% rename from templates/emails/trial-end.txt rename to templates/emails/transactional/trial-end.txt From e29021f46a04cefe3a6d0e39019bf24481dc955a Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 2 Feb 2020 21:49:38 +0700 Subject: [PATCH 02/14] black format --- app/email_utils.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/email_utils.py b/app/email_utils.py index 0bb88150..4e917b46 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -52,10 +52,16 @@ def send_activation_email(email, name, activation_link): email, f"Just one more step to join SimpleLogin {name}", _render( - "transactional/activation.txt", name=name, activation_link=activation_link, email=email + "transactional/activation.txt", + name=name, + activation_link=activation_link, + email=email, ), _render( - "transactional/activation.html", name=name, activation_link=activation_link, email=email + "transactional/activation.html", + name=name, + activation_link=activation_link, + email=email, ), ) @@ -65,10 +71,14 @@ def send_reset_password_email(email, name, reset_password_link): email, f"Reset your password on SimpleLogin", _render( - "transactional/reset-password.txt", name=name, reset_password_link=reset_password_link + "transactional/reset-password.txt", + name=name, + reset_password_link=reset_password_link, ), _render( - "transactional/reset-password.html", name=name, reset_password_link=reset_password_link + "transactional/reset-password.html", + name=name, + reset_password_link=reset_password_link, ), ) @@ -142,7 +152,10 @@ def send_cannot_create_domain_alias(user, alias, domain): user.email, f"Alias {alias} cannot be created", _render( - "transactional/cannot-create-alias-domain.txt", name=user.name, alias=alias, domain=domain + "transactional/cannot-create-alias-domain.txt", + name=user.name, + alias=alias, + domain=domain, ), _render( "transactional/cannot-create-alias-domain.html", From 3cbaab41f3aecee618631b4b82cce41c508037ad Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 2 Feb 2020 22:17:04 +0700 Subject: [PATCH 03/14] support --job argument in cron: stats, notify_trial_end --- cron.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cron.py b/cron.py index cc2fae0e..49e1880c 100644 --- a/cron.py +++ b/cron.py @@ -1,3 +1,5 @@ +import argparse + import arrow from app.config import IGNORED_EMAILS, ADMIN_EMAIL @@ -16,7 +18,7 @@ from app.models import ( from server import create_app -def send_trial_end_soon(): +def notify_trial_end(): for user in User.query.filter(User.trial_end.isnot(None)).all(): if arrow.now().shift(days=3) > user.trial_end >= arrow.now().shift(days=2): LOG.d("Send trial end email to user %s", user) @@ -106,8 +108,20 @@ nb_app: {nb_app}
if __name__ == "__main__": LOG.d("Start running cronjob") + parser = argparse.ArgumentParser() + parser.add_argument( + "-j", + "--job", + help="Choose a cron job to run", + type=str, + choices=["stats", "notify_trial_end",], + ) + args = parser.parse_args() + app = create_app() with app.app_context(): - stats() - send_trial_end_soon() + if args.job == "stats": + stats() + elif args.job == "notify_trial_end": + notify_trial_end() From 88b96b0dbfa9c5d57872a761f5c1033b03dad9a5 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 2 Feb 2020 23:38:19 +0700 Subject: [PATCH 04/14] Add entries for stat and notify_trial_end in crontab --- crontab.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crontab.yml b/crontab.yml index 579277c2..373138ae 100644 --- a/crontab.yml +++ b/crontab.yml @@ -1,6 +1,12 @@ jobs: - - name: SimpleLogin cronjob - command: python /code/cron.py + - name: SimpleLogin stats + command: python /code/cron.py -j stats shell: /bin/bash schedule: "0 0 * * *" captureStderr: true + + - name: SimpleLogin Notify Trial Ends + command: python /code/cron.py -j notify_trial_end + shell: /bin/bash + schedule: "0 8 * * *" + captureStderr: true From cefeb972a22ad39bb272e45e6826256e86449711 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 2 Feb 2020 23:38:46 +0700 Subject: [PATCH 05/14] create onboarding-1 email --- templates/emails/com/onboarding-1.html | 19 +++++++++++++++++++ templates/emails/com/onboarding-1.txt | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 templates/emails/com/onboarding-1.html create mode 100644 templates/emails/com/onboarding-1.txt diff --git a/templates/emails/com/onboarding-1.html b/templates/emails/com/onboarding-1.html new file mode 100644 index 00000000..ec1625d6 --- /dev/null +++ b/templates/emails/com/onboarding-1.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block content %} + {{ render_text("Hi " + user.name) }} + {{ render_text("Do you know you can send emails to anyone from your alias?") }} + {{ render_text("This below Youtube video quickly walks you through the steps, make sure to turn the caption on.") }} + {{ render_text('') }} + {{ render_text('If the video doesn\'t play, its link is https://www.youtube.com/watch?v=cZ-J_lwv-YQ') }} + + {{ render_text("Here are the steps:") }} + {{ render_text("1. First click Send Email on your alias you want to send email from.") }} + {{ render_text("2. Enter your contact email, this will generate a reverse-alias.") }} + {{ render_text("3. Use this reverse-alias instead of your contact email.") }} + {{ render_text("4. Your contact will receive this email from your alias.") }} + + {{ render_text("As usual, let me know if you have any question.") }} + +{% endblock %} + diff --git a/templates/emails/com/onboarding-1.txt b/templates/emails/com/onboarding-1.txt new file mode 100644 index 00000000..dbe8df44 --- /dev/null +++ b/templates/emails/com/onboarding-1.txt @@ -0,0 +1,21 @@ +Hi {{name}} + +Do you know you can send an email to anyone from your alias? +This below Youtube video walks you quickly through the steps, make sure to turn the caption on. + +https://www.youtube.com/watch?v=cZ-J_lwv-YQ&feature=youtu.be + +Here are the steps: +1. First click "Send Email" on your alias you want to send email from +2. Enter your contact email, this will generate an "reverse-alias" +3. Use this reverse-alias instead of your contact email +4. Your contact will receive this email from your alias. + +As usual, let me know if you have any question. + +Best regards, +Son - SimpleLogin founder. + +--- +This email was sent to {email} and is part of our onboarding series. +Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification \ No newline at end of file From 28b349e1d34e302a4ee39dc20ed89cc204a9d263 Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:00:58 +0700 Subject: [PATCH 06/14] rename _render -> render --- app/email_utils.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/email_utils.py b/app/email_utils.py index 4e917b46..3934d51b 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -20,7 +20,7 @@ from app.config import ( from app.log import LOG -def _render(template_name, **kwargs) -> str: +def render(template_name, **kwargs) -> str: templates_dir = os.path.join(ROOT_DIR, "templates", "emails") env = Environment(loader=FileSystemLoader(templates_dir)) @@ -33,8 +33,8 @@ def send_welcome_email(user): send_email( user.email, f"Welcome to SimpleLogin {user.name}", - _render("com/welcome.txt", name=user.name, user=user), - _render("com/welcome.html", name=user.name, user=user), + render("com/welcome.txt", name=user.name, user=user), + render("com/welcome.html", name=user.name, user=user), ) @@ -42,8 +42,8 @@ def send_trial_end_soon_email(user): send_email( user.email, f"Your trial will end soon {user.name}", - _render("transactional/trial-end.txt", name=user.name, user=user), - _render("transactional/trial-end.html", name=user.name, user=user), + render("transactional/trial-end.txt", name=user.name, user=user), + render("transactional/trial-end.html", name=user.name, user=user), ) @@ -51,13 +51,13 @@ def send_activation_email(email, name, activation_link): send_email( email, f"Just one more step to join SimpleLogin {name}", - _render( + render( "transactional/activation.txt", name=name, activation_link=activation_link, email=email, ), - _render( + render( "transactional/activation.html", name=name, activation_link=activation_link, @@ -70,12 +70,12 @@ def send_reset_password_email(email, name, reset_password_link): send_email( email, f"Reset your password on SimpleLogin", - _render( + render( "transactional/reset-password.txt", name=name, reset_password_link=reset_password_link, ), - _render( + render( "transactional/reset-password.html", name=name, reset_password_link=reset_password_link, @@ -87,14 +87,14 @@ def send_change_email(new_email, current_email, name, link): send_email( new_email, f"Confirm email update on SimpleLogin", - _render( + render( "transactional/change-email.txt", name=name, link=link, new_email=new_email, current_email=current_email, ), - _render( + render( "transactional/change-email.html", name=name, link=link, @@ -108,8 +108,8 @@ def send_new_app_email(email, name): send_email( email, f"Any question/feedback for SimpleLogin {name}?", - _render("com/new-app.txt", name=name), - _render("com/new-app.html", name=name), + render("com/new-app.txt", name=name), + render("com/new-app.html", name=name), ) @@ -117,8 +117,8 @@ def send_test_email_alias(email, name): send_email( email, f"This email is sent to {email}", - _render("transactional/test-email.txt", name=name, alias=email), - _render("transactional/test-email.html", name=name, alias=email), + render("transactional/test-email.txt", name=name, alias=email), + render("transactional/test-email.html", name=name, alias=email), ) @@ -129,13 +129,13 @@ def send_cannot_create_directory_alias(user, alias, directory): send_email( user.email, f"Alias {alias} cannot be created", - _render( + render( "transactional/cannot-create-alias-directory.txt", name=user.name, alias=alias, directory=directory, ), - _render( + render( "transactional/cannot-create-alias-directory.html", name=user.name, alias=alias, @@ -151,13 +151,13 @@ def send_cannot_create_domain_alias(user, alias, domain): send_email( user.email, f"Alias {alias} cannot be created", - _render( + render( "transactional/cannot-create-alias-domain.txt", name=user.name, alias=alias, domain=domain, ), - _render( + render( "transactional/cannot-create-alias-domain.html", name=user.name, alias=alias, @@ -174,14 +174,14 @@ def send_reply_alias_must_use_personal_email(user, alias, sender): send_email( user.email, f"Reply from your alias {alias} only works with your personal email", - _render( + render( "transactional/reply-must-use-personal-email.txt", name=user.name, alias=alias, sender=sender, user_email=user.email, ), - _render( + render( "transactional/reply-must-use-personal-email.html", name=user.name, alias=alias, From 7c9896c0dfd2a8556a388055963f59a4f66181bc Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:09:15 +0700 Subject: [PATCH 07/14] update onboarding-1 email --- templates/emails/com/onboarding-1.html | 14 ++++++++++---- templates/emails/com/onboarding-1.txt | 18 +++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/templates/emails/com/onboarding-1.html b/templates/emails/com/onboarding-1.html index ec1625d6..7f498ce8 100644 --- a/templates/emails/com/onboarding-1.html +++ b/templates/emails/com/onboarding-1.html @@ -1,11 +1,17 @@ {% extends "base.html" %} {% block content %} + {{ render_text("This email is sent to " + user.email + " and is part of our onboarding series.") }} + + {{ render_text('Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification') }} + + {{ render_text("
") }} + {{ render_text("Hi " + user.name) }} {{ render_text("Do you know you can send emails to anyone from your alias?") }} - {{ render_text("This below Youtube video quickly walks you through the steps, make sure to turn the caption on.") }} - {{ render_text('') }} - {{ render_text('If the video doesn\'t play, its link is https://www.youtube.com/watch?v=cZ-J_lwv-YQ') }} + {{ render_text("This Youtube video quickly walks you through the steps:") }} + + {{ render_text('https://youtu.be/VsypF-DBaow') }} {{ render_text("Here are the steps:") }} {{ render_text("1. First click Send Email on your alias you want to send email from.") }} @@ -13,7 +19,7 @@ {{ render_text("3. Use this reverse-alias instead of your contact email.") }} {{ render_text("4. Your contact will receive this email from your alias.") }} - {{ render_text("As usual, let me know if you have any question.") }} + {{ render_text("As usual, let me know if you have any question by replying to this email.") }} {% endblock %} diff --git a/templates/emails/com/onboarding-1.txt b/templates/emails/com/onboarding-1.txt index dbe8df44..01ec9564 100644 --- a/templates/emails/com/onboarding-1.txt +++ b/templates/emails/com/onboarding-1.txt @@ -1,9 +1,13 @@ -Hi {{name}} +This email is sent to {{ user.email }} and is part of our onboarding series. +Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification +---------------- + +Hi {{user.name}} Do you know you can send an email to anyone from your alias? -This below Youtube video walks you quickly through the steps, make sure to turn the caption on. +This below Youtube video walks you quickly through the steps: -https://www.youtube.com/watch?v=cZ-J_lwv-YQ&feature=youtu.be +https://youtu.be/VsypF-DBaow Here are the steps: 1. First click "Send Email" on your alias you want to send email from @@ -11,11 +15,7 @@ Here are the steps: 3. Use this reverse-alias instead of your contact email 4. Your contact will receive this email from your alias. -As usual, let me know if you have any question. +As usual, let me know if you have any question by replying to this email. Best regards, -Son - SimpleLogin founder. - ---- -This email was sent to {email} and is part of our onboarding series. -Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification \ No newline at end of file +Son - SimpleLogin founder. \ No newline at end of file From ffee8757ea397616ea996a4dfa97fa4fd536c0fc Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:09:48 +0700 Subject: [PATCH 08/14] add Job model --- app/models.py | 14 +++++++ .../versions/2020_020313_9c976df9b9c4_.py | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 migrations/versions/2020_020313_9c976df9b9c4_.py diff --git a/app/models.py b/app/models.py index 0a5ef506..6577d962 100644 --- a/app/models.py +++ b/app/models.py @@ -769,3 +769,17 @@ class Directory(db.Model, ModelMixin): def __repr__(self): return f"" + + +class Job(db.Model, ModelMixin): + """Used to schedule one-time job in the future""" + + name = db.Column(db.String(128), nullable=False) + payload = db.Column(db.JSON) + + # whether the job has been taken by the job runner + taken = db.Column(db.Boolean, default=False, nullable=False) + run_at = db.Column(ArrowType) + + def __repr__(self): + return f"" diff --git a/migrations/versions/2020_020313_9c976df9b9c4_.py b/migrations/versions/2020_020313_9c976df9b9c4_.py new file mode 100644 index 00000000..0badff55 --- /dev/null +++ b/migrations/versions/2020_020313_9c976df9b9c4_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 9c976df9b9c4 +Revises: 7c39ba4ec38d +Create Date: 2020-02-03 13:08:29.049797 + +""" +import sqlalchemy_utils +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9c976df9b9c4' +down_revision = '7c39ba4ec38d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('job', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False), + sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True), + sa.Column('name', sa.String(length=128), nullable=False), + sa.Column('payload', sa.JSON(), nullable=True), + sa.Column('taken', sa.Boolean(), nullable=False), + sa.Column('run_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('job') + # ### end Alembic commands ### From 61229200a58b75fd3fbc40c2597e2a7d44e1718d Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:11:11 +0700 Subject: [PATCH 09/14] create job runner --- app/config.py | 4 +++ job_runner.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 job_runner.py diff --git a/app/config.py b/app/config.py index 0d35ef52..6c0f4d36 100644 --- a/app/config.py +++ b/app/config.py @@ -166,3 +166,7 @@ MFA_USER_ID = "mfa_user_id" FLASK_PROFILER_PATH = os.environ.get("FLASK_PROFILER_PATH") FLASK_PROFILER_PASSWORD = os.environ.get("FLASK_PROFILER_PASSWORD") + + +# Job names +JOB_ONBOARDING_1 = "onboarding-1" diff --git a/job_runner.py b/job_runner.py new file mode 100644 index 00000000..f9839070 --- /dev/null +++ b/job_runner.py @@ -0,0 +1,77 @@ +""" +Run scheduled jobs. +Not meant for running job at precise time (+- 1h) +""" +import time + +import arrow + +from app.config import ( + JOB_ONBOARDING_1, +) +from app.email_utils import ( + send_email, + render, +) +from app.extensions import db +from app.log import LOG +from app.models import ( + User, + Job, +) +from server import create_app + + +# fix the database connection leak issue +# use this method instead of create_app +def new_app(): + app = create_app() + + @app.teardown_appcontext + def shutdown_session(response_or_exc): + # same as shutdown_session() in flask-sqlalchemy but this is not enough + db.session.remove() + + # dispose the engine too + db.engine.dispose() + + return app + + +def onboarding_1(user): + send_email( + user.email, + f"Do you know you can send emails to anyone from your alias?", + render("com/onboarding-1.txt", user=user), + render("com/onboarding-1.html", user=user), + ) + + +if __name__ == "__main__": + while True: + # run a job 1h earlier or later is not a big deal ... + min_dt = arrow.now().shift(hours=-1) + max_dt = arrow.now().shift(hours=1) + + app = new_app() + + with app.app_context(): + for job in Job.query.filter( + Job.taken == False, Job.run_at > min_dt, Job.run_at <= max_dt + ).all(): + LOG.d("Take job %s", job) + + # mark the job as taken, whether it will be executed successfully or not + job.taken = True + db.session.commit() + + if job.name == JOB_ONBOARDING_1: + user_id = job.payload.get("user_id") + user = User.get(user_id) + + LOG.d("run onboarding_1 for user %s", user) + onboarding_1(user) + else: + LOG.error("Unknown job name %s", job.name) + + time.sleep(10) From 0f4e803639952e7f149d2098e54a8bae4ebe342c Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:11:38 +0700 Subject: [PATCH 10/14] WIP send user onboarding email 1day after the account creation --- app/models.py | 16 +++++++++++++++- cron.py | 2 ++ job_runner.py | 4 +--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/models.py b/app/models.py index 6577d962..bc36729e 100644 --- a/app/models.py +++ b/app/models.py @@ -10,7 +10,13 @@ from sqlalchemy import text, desc from sqlalchemy_utils import ArrowType from app import s3 -from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN, URL, AVATAR_URL_EXPIRATION +from app.config import ( + EMAIL_DOMAIN, + MAX_NB_EMAIL_FREE_PLAN, + URL, + AVATAR_URL_EXPIRATION, + JOB_ONBOARDING_1, +) from app.email_utils import get_email_name from app.extensions import db from app.log import LOG @@ -144,6 +150,14 @@ class User(db.Model, ModelMixin, UserMixin): GenEmail.create_new(user.id, prefix="my-first-alias") db.session.flush() + # Schedule onboarding emails + Job.create( + name=JOB_ONBOARDING_1, + payload={"user_id": user.id}, + run_at=arrow.now().shift(days=1), + ) + db.session.flush() + return user def lifetime_or_active_subscription(self) -> bool: diff --git a/cron.py b/cron.py index 49e1880c..8562426f 100644 --- a/cron.py +++ b/cron.py @@ -122,6 +122,8 @@ if __name__ == "__main__": with app.app_context(): if args.job == "stats": + LOG.d("Compute stats") stats() elif args.job == "notify_trial_end": + LOG.d("Notify users with trial ending soon") notify_trial_end() diff --git a/job_runner.py b/job_runner.py index f9839070..3bf1d8a3 100644 --- a/job_runner.py +++ b/job_runner.py @@ -6,9 +6,7 @@ import time import arrow -from app.config import ( - JOB_ONBOARDING_1, -) +from app.config import JOB_ONBOARDING_1 from app.email_utils import ( send_email, render, From db651aa1ea7d60fc1903b3b078e0a14f4bb39e7c Mon Sep 17 00:00:00 2001 From: Son NK Date: Mon, 3 Feb 2020 13:46:47 +0700 Subject: [PATCH 11/14] add Safari extension newsletter --- templates/emails/com/safari-extension.html | 19 +++++++++++++++++++ templates/emails/com/safari-extension.txt | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 templates/emails/com/safari-extension.html create mode 100644 templates/emails/com/safari-extension.txt diff --git a/templates/emails/com/safari-extension.html b/templates/emails/com/safari-extension.html new file mode 100644 index 00000000..bbb757e5 --- /dev/null +++ b/templates/emails/com/safari-extension.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block content %} + {{ render_text("This email is sent to " + user.email + ".") }} + + {{ render_text('Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification') }} + + {{ render_text("
") }} + + {{ render_text("Hi " + user.name) }} + {{ render_text("If you use Safari on a MacBook or iMac, you should check out our new Safari extension.") }} + {{ render_text('It can be installed on AppStore.') }} + + {{ render_text('') }} + + {{ render_text("As usual, let me know if you have any question by replying to this email.") }} + +{% endblock %} + diff --git a/templates/emails/com/safari-extension.txt b/templates/emails/com/safari-extension.txt new file mode 100644 index 00000000..d3cc2ecc --- /dev/null +++ b/templates/emails/com/safari-extension.txt @@ -0,0 +1,16 @@ +This email is sent to {{ user.email }}. +Unsubscribe from our emails on https://app.simplelogin.io/dashboard/setting#notification +---------------- + +Hi {{user.name}} + +If you use Safari on a MacBook or iMac, you should check out our new Safari extension. + +It can be installed on: + +https://apps.apple.com/us/app/simplelogin/id1494051017?mt=12&fbclid=IwAR0M0nnEKgoieMkmx91TSXrtcScj7GouqRxGgXeJz2un_5ydhIKlbAI79Io + +As usual, let me know if you have any question by replying to this email. + +Best regards, +Son - SimpleLogin founder. \ No newline at end of file From f5a2eb387b8faa7b35557ff89aaa6b5866e4222c Mon Sep 17 00:00:00 2001 From: Son NK Date: Tue, 4 Feb 2020 14:36:43 +0700 Subject: [PATCH 12/14] take into account user.notification setting in onboarding email --- job_runner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/job_runner.py b/job_runner.py index 3bf1d8a3..0c412112 100644 --- a/job_runner.py +++ b/job_runner.py @@ -37,6 +37,10 @@ def new_app(): def onboarding_1(user): + if not user.notification: + LOG.d("User %s disable notification setting", user) + return + send_email( user.email, f"Do you know you can send emails to anyone from your alias?", From d3a8e3529eefedf1c65efca476b30545c5796047 Mon Sep 17 00:00:00 2001 From: Son NK Date: Tue, 4 Feb 2020 14:44:19 +0700 Subject: [PATCH 13/14] add more info to Telegram github action --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e5bda6d6..8f86deb4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,4 +45,4 @@ jobs: with: to: ${{ secrets.TELEGRAM_TO }} token: ${{ secrets.TELEGRAM_TOKEN }} - args: New Docker image pushed + args: Docker image pushed on ${{ github.ref }} From c7903d534a0caaf900c18f4069ad22164160fe1d Mon Sep 17 00:00:00 2001 From: Son NK Date: Tue, 4 Feb 2020 18:32:57 +0700 Subject: [PATCH 14/14] use null instead of "" in /api/auth/login --- app/api/views/auth_login.py | 4 ++-- tests/api/test_auth_login.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/api/views/auth_login.py b/app/api/views/auth_login.py index 173bb5eb..c4bce079 100644 --- a/app/api/views/auth_login.py +++ b/app/api/views/auth_login.py @@ -54,11 +54,11 @@ def auth_login(): if user.enable_otp: s = Signer(FLASK_SECRET) ret["mfa_key"] = s.sign(str(user.id)) - ret["api_key"] = "" + ret["api_key"] = None else: api_key = ApiKey.create(user.id, device) db.session.commit() - ret["mfa_key"] = "" + ret["mfa_key"] = None ret["api_key"] = api_key.code return jsonify(**ret), 200 diff --git a/tests/api/test_auth_login.py b/tests/api/test_auth_login.py index 0dbfb4a3..59f89024 100644 --- a/tests/api/test_auth_login.py +++ b/tests/api/test_auth_login.py @@ -16,7 +16,7 @@ def test_auth_login_success_mfa_disabled(flask_client): assert r.status_code == 200 assert r.json["api_key"] assert r.json["mfa_enabled"] == False - assert r.json["mfa_key"] == "" + assert r.json["mfa_key"] is None assert r.json["name"] == "Test User" @@ -36,7 +36,7 @@ def test_auth_login_success_mfa_enabled(flask_client): ) assert r.status_code == 200 - assert r.json["api_key"] == "" + assert r.json["api_key"] is None assert r.json["mfa_enabled"] == True assert r.json["mfa_key"] assert r.json["name"] == "Test User"