mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
feat: add template based email content (html + plaintext fallback)
This commit is contained in:
@@ -81,6 +81,21 @@ MIDDLEWARE = [
|
|||||||
"django_structlog.middlewares.RequestMiddleware",
|
"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"
|
ROOT_URLCONF = "config.urls"
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ from datetime import UTC, datetime
|
|||||||
import structlog
|
import structlog
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
from config import settings
|
from config import settings
|
||||||
|
from config.settings import FRONTEND_URLS
|
||||||
from letters.models import Letter
|
from letters.models import Letter
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
@@ -23,11 +25,28 @@ def notify_unlocked_letter(letter):
|
|||||||
"""
|
"""
|
||||||
author = letter.user.get_username()
|
author = letter.user.get_username()
|
||||||
try:
|
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.notified_at = datetime.now(UTC)
|
||||||
letter.save()
|
letter.save()
|
||||||
except Exception:
|
logger.info(f"Successfully notified {author} of unlocked letter")
|
||||||
logger.exception(f"Failed to notify {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():
|
def vault_unlock_notification_polling_scheduler():
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends 'email/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div style="padding: 15px; font-style: italic">
|
||||||
|
<p>{{ pen_name }},</p>
|
||||||
|
<p>
|
||||||
|
Your destination is one train away.
|
||||||
|
</p>
|
||||||
|
<p>I've been keeping a place for your words.<br/>
|
||||||
|
Come when you're ready.</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footnote %}
|
||||||
|
This link expires in 24 hours.<br/>
|
||||||
|
I'm patient, but not endlessly so.
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
Didn't write to me? Then someone else did.<br/>
|
||||||
|
Ignore this. I'll forget you were ever here.
|
||||||
|
{% endblock %}
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>pi. ku.</title>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0; padding:0; background-color:#1a1712;">
|
||||||
|
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="background-color:#1a1712;">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding: 48px 16px;">
|
||||||
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
style="max-width:480px; width:100%;">
|
||||||
|
|
||||||
|
{# Logo #}
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="padding-bottom: 36px;">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/ramvignesh-b/cdn@main/pi-ku_logo.png" width="100"
|
||||||
|
height="50" alt="Pi.Ku"
|
||||||
|
style="display:block;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{# Body #}
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Lora, Georgia, serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: #cdccca;
|
||||||
|
font-style: italic;
|
||||||
|
padding-bottom: 32px;">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{# CTA #}
|
||||||
|
{% if cta %}
|
||||||
|
<tr>
|
||||||
|
<td align="left" style="padding-bottom: 32px;">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td style="background-color: #301e19; border-radius: 3px;">
|
||||||
|
<a href='{{ cta.link }}'
|
||||||
|
style="display: inline-block;
|
||||||
|
padding: 12px 28px;
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #f5e6c8;
|
||||||
|
text-decoration: none;
|
||||||
|
letter-spacing: 0.04em;">
|
||||||
|
{{ cta.title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if footnote %}
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Lora, Georgia, serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #7a7974;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
line-height: 1.8;">
|
||||||
|
{% block footnote %}
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Footer #}
|
||||||
|
<tr>
|
||||||
|
<td style="border-top: 1px solid #2e2c29; padding-bottom: 24px; font-size: 0;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Lora, serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #5a5957;
|
||||||
|
line-height: 1.8;">
|
||||||
|
{% block footer %}
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'email/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>
|
||||||
|
Time has a way of making things clearer.<br/>
|
||||||
|
Or heavier. Sometimes both.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You had something to say at this exact moment.<br/>
|
||||||
|
I kept it exactly as you left it. <br/>
|
||||||
|
Not a word changed. Not a word read.
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footnote %}
|
||||||
|
<p>
|
||||||
|
You're ready now. Or maybe you're still not.<br/>
|
||||||
|
Open it anyway. You won't regret it.
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
@@ -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.
|
||||||
+13
-10
@@ -1,6 +1,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.core.mail import send_mail
|
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.encoding import force_bytes
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
|
|
||||||
@@ -9,16 +10,18 @@ def send_activation_email(user):
|
|||||||
token = default_token_generator.make_token(user)
|
token = default_token_generator.make_token(user)
|
||||||
uid = urlsafe_base64_encode(force_bytes(user.public_id))
|
uid = urlsafe_base64_encode(force_bytes(user.public_id))
|
||||||
activation_url = f"{settings.FRONTEND_URLS[0]}/activate/{uid}/{token}"
|
activation_url = f"{settings.FRONTEND_URLS[0]}/activate/{uid}/{token}"
|
||||||
subject = "Activate Your Piku Account"
|
subject = "Activate your pi. ku. account"
|
||||||
message = f"""Hi {user.full_name},
|
context = {
|
||||||
|
"pen_name": user.full_name,
|
||||||
Welcome to Pi Ku.
|
"footnote": True,
|
||||||
|
"cta": {
|
||||||
Please click the link below to activate your account:
|
"title": "Onboard",
|
||||||
>> {activation_url}
|
"link": 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)
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user