diff --git a/backend/config/settings.py b/backend/config/settings.py index 14b0fef..5a6992c 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -81,6 +81,21 @@ MIDDLEWARE = [ "django_structlog.middlewares.RequestMiddleware", ] +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] ROOT_URLCONF = "config.urls" diff --git a/backend/letters/tasks.py b/backend/letters/tasks.py index d794018..7c056af 100644 --- a/backend/letters/tasks.py +++ b/backend/letters/tasks.py @@ -3,8 +3,10 @@ from datetime import UTC, datetime import structlog from apscheduler.schedulers.background import BackgroundScheduler from django.core.mail import send_mail +from django.template.loader import render_to_string from config import settings +from config.settings import FRONTEND_URLS from letters.models import Letter logger = structlog.get_logger(__name__) @@ -23,11 +25,28 @@ def notify_unlocked_letter(letter): """ author = letter.user.get_username() try: - send_mail(subject="", message="", from_email=settings.FROM_EMAIL, recipient_list=[author], fail_silently=False) + letter_link = f"{FRONTEND_URLS[0]}/read/{letter.public_id}" + subject = "A letter. Written for this exact moment." + context = { + "pen_name": letter.user.first_name, + "cta": {"title": "View what you wrote", "link": letter_link}, + "footnote": True, + } + plaint_content = render_to_string("email/vault_unlock.txt", context=context) + html_content = render_to_string("email/vault_unlock.html", context=context) + send_mail( + subject, + message=plaint_content, + from_email=settings.FROM_EMAIL, + recipient_list=[author], + fail_silently=False, + html_message=html_content, + ) letter.notified_at = datetime.now(UTC) letter.save() - except Exception: - logger.exception(f"Failed to notify {author} of unlocked letter") + logger.info(f"Successfully notified {author} of unlocked letter") + except Exception as e: + logger.exception(f"Failed to notify {author} of unlocked letter", str(e)) def vault_unlock_notification_polling_scheduler(): diff --git a/backend/templates/email/activation.html b/backend/templates/email/activation.html new file mode 100644 index 0000000..5135d5f --- /dev/null +++ b/backend/templates/email/activation.html @@ -0,0 +1,22 @@ +{% extends 'email/base.html' %} + +{% block content %} +
+

{{ pen_name }},

+

+ Your destination is one train away. +

+

I've been keeping a place for your words.
+ Come when you're ready.

+
+{% endblock %} + +{% block footnote %} + This link expires in 24 hours.
+ I'm patient, but not endlessly so. +{% endblock %} + +{% block footer %} + Didn't write to me? Then someone else did.
+ Ignore this. I'll forget you were ever here. +{% endblock %} \ No newline at end of file diff --git a/backend/templates/email/activation.txt b/backend/templates/email/activation.txt new file mode 100644 index 0000000..d115ffd --- /dev/null +++ b/backend/templates/email/activation.txt @@ -0,0 +1,21 @@ +pi. ku. +------------------------------------------- + +{{pen_name}}, + +Your destination is one train away. + +I've been keeping a place for your words. +Come when you're ready. + +{{ cta.title }} -> {{ cta.link }} + +------------------------------------------- + +This link expires in 24 hours. +I'm patient, but not endlessly so. + +------------------------------------------- + +Didn't write to me? Then someone else did. +Ignore this. I'll forget you were ever here. \ No newline at end of file diff --git a/backend/templates/email/base.html b/backend/templates/email/base.html new file mode 100644 index 0000000..09460b9 --- /dev/null +++ b/backend/templates/email/base.html @@ -0,0 +1,100 @@ + + + + + + + + pi. ku. + + + + + + + +
+ + + {# Logo #} + + + + + {# Body #} + + + + + {# CTA #} + {% if cta %} + + + + {% endif %} + + {% if footnote %} + + + + {% endif %} + + {# Footer #} + + + + + + + +
+ Pi.Ku +
+ {% block content %} + {% endblock %} +
+ + + + +
+ + {{ cta.title }} + +
+
+ {% block footnote %} + {% endblock %} +
 
+ {% block footer %} + {% endblock %} +
+
+ + + \ No newline at end of file diff --git a/backend/templates/email/vault_unlock.html b/backend/templates/email/vault_unlock.html new file mode 100644 index 0000000..8f8f0f4 --- /dev/null +++ b/backend/templates/email/vault_unlock.html @@ -0,0 +1,20 @@ +{% extends 'email/base.html' %} + +{% block content %} +

+ Time has a way of making things clearer.
+ Or heavier. Sometimes both. +

+

+ You had something to say at this exact moment.
+ I kept it exactly as you left it.
+ Not a word changed. Not a word read. +

+{% endblock %} + +{% block footnote %} +

+ You're ready now. Or maybe you're still not.
+ Open it anyway. You won't regret it. +

+{% endblock %} \ No newline at end of file diff --git a/backend/templates/email/vault_unlock.txt b/backend/templates/email/vault_unlock.txt new file mode 100644 index 0000000..0b93a64 --- /dev/null +++ b/backend/templates/email/vault_unlock.txt @@ -0,0 +1,17 @@ +pi. ku. +------------------------------------------- + +{{pen_name}}, + +Time has a way of making things clearer. +Or heavier. Sometimes both. + +You had something to say at this exact moment. +I kept it exactly as you left it. +Not a word changed. Not a word read. + +{{ cta.title }} -> {{ cta.link }} + +------------------------------------------- +You're ready now. Or maybe you're still not. +Open it anyway. You won't regret it. \ No newline at end of file diff --git a/backend/users/utils.py b/backend/users/utils.py index 38d302c..c1816ad 100644 --- a/backend/users/utils.py +++ b/backend/users/utils.py @@ -1,6 +1,7 @@ from django.conf import settings from django.contrib.auth.tokens import default_token_generator from django.core.mail import send_mail +from django.template.loader import render_to_string from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode @@ -9,16 +10,18 @@ def send_activation_email(user): token = default_token_generator.make_token(user) uid = urlsafe_base64_encode(force_bytes(user.public_id)) activation_url = f"{settings.FRONTEND_URLS[0]}/activate/{uid}/{token}" - subject = "Activate Your Piku Account" - message = f"""Hi {user.full_name}, - - Welcome to Pi Ku. - - Please click the link below to activate your account: - >> {activation_url} - - If you did not create this account, please ignore this email.""" - send_mail(subject, message, settings.FROM_EMAIL, [user.email], fail_silently=False) + subject = "Activate your pi. ku. account" + context = { + "pen_name": user.full_name, + "footnote": True, + "cta": { + "title": "Onboard", + "link": activation_url, + }, + } + html_content = render_to_string("email/activation.html", context) + plain_content = render_to_string("email/activation.txt", context) + send_mail(subject, plain_content, settings.FROM_EMAIL, [user.email], fail_silently=False, html_message=html_content) return True