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 #}
+
+ |
+ {% block content %}
+ {% endblock %}
+ |
+
+
+ {# CTA #}
+ {% if cta %}
+
+ |
+
+ |
+
+ {% endif %}
+
+ {% if footnote %}
+
+ |
+ {% block footnote %}
+ {% endblock %}
+ |
+
+ {% endif %}
+
+ {# Footer #}
+
+ | |
+
+
+ |
+ {% 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