Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env-dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ ENVIRONMENT=local
# EMAIL
[email protected]
FROM_EMAIL_NAME=Ideological Atlas
BASE_SITE_URL=localhost:4045
BASE_SITE_URL=http://localhost:3000

# TESTING
TEST_EMAIL=CHANGE-ME
TEST_LANGUAGE=es
TEST_TEMPLATE=register
TEST_CONTEXT={"user_uuid": "test-uuid-123", "name": "Test User"}
TEST_CONTEXT={"verification_token": "test-uuid-123", "name": "Test User"}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ uv run uvicorn main:app --host 0.0.0.0 --port 5051 --reload
"template_name": "register",
"language": "es",
"context": {
"user_uuid": "123e4567-e89b-12d3-a456-426614174000",
"verification_token": "EXAMPLE-TOKEN",
"name": "Jane Doe"
}
}
Expand All @@ -189,7 +189,7 @@ Or manually:
curl -X POST http://localhost:5051/notifications/send \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_SECRET_KEY" \
-d '{"to_email": "[email protected]", "template_name": "register", "language": "en", "context": {"user_uuid": "123"}}'
-d '{"to_email": "[email protected]", "template_name": "register", "language": "en", "context": {"verification_token": "EXAMPLE-TOKEN"}}'

```

Expand Down
25 changes: 25 additions & 0 deletions src/app/core/theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import dataclass


@dataclass
class Theme:
background: str = "#020617"
foreground: str = "#f8fafc"

card: str = "#0f172a"
card_foreground: str = "#f8fafc"
border: str = "#1e293b"

primary: str = "#338f39"
primary_foreground: str = "#ffffff"

secondary: str = "#1e293b"
secondary_foreground: str = "#f8fafc"

muted_foreground: str = "#94a3b8"

destructive: str = "#ef4444"
destructive_foreground: str = "#ffffff"


theme = Theme()
2 changes: 2 additions & 0 deletions src/app/services/email_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from jinja2 import Environment, FileSystemLoader, TemplateNotFound

from app.core.config import settings
from app.core.theme import theme

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -58,6 +59,7 @@ def _render_template(
"project_name": settings.PROJECT_NAME,
"t": translations,
"year": datetime.now().year,
"theme": theme,
}
final_context = {**global_context, **specific_context}

Expand Down
101 changes: 63 additions & 38 deletions src/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,71 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block subject %}{{ t.base.subject_prefix }}{% endblock %}</title>
<style type="text/css">
body { margin: 0; padding: 0; min-width: 100%; width: 100% !important; height: 100% !important;}
body, table, td, div, p, a { -webkit-font-smoothing: antialiased; text-size-adjust: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; line-height: 1.5; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-collapse: collapse !important; border-spacing: 0; }
img { border: 0; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }
#outlook a { padding: 0; }
.ReadMsgBody { width: 100%; } .ExternalClass { width: 100%; }
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div { line-height: 100%; }
/* Client-specific resets */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }

/* General Resets */
body { margin: 0; padding: 0; width: 100% !important; height: 100% !important; }

/* Theme Styles */
/* Body has NO background color set, so it uses the email client default */
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: {{ theme.foreground }}; }
a { color: {{ theme.primary }}; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table class="email-container" width="600" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background-color: #ffffff; max-width: 600px; width: 100%; border-radius: 8px; overflow: hidden; box-shadow: 0px 2px 8px rgba(0,0,0,0.05);">

<tr>
<td style="padding: 30px; color: #333333; font-size: 16px;">
{% block content %}{% endblock %}
</td>
</tr>
<tr>
<td style="background-color: #F8F9FA; padding: 25px; font-size: 12px; line-height: 1.6; color: #777777; border-top: 3px solid #1F4E79; text-align: center;">
<p style="margin: 0 0 15px 0;">
<strong>{{ t.base.doubts }}</strong><br>
{{ t.base.contact_text }} <a href="mailto:{{ t.base.contact_email }}" style="color: #1F4E79; text-decoration: none; font-weight: bold;">{{ t.base.contact_email }}</a> {{ t.base.help_text }}
</p>
<p style="margin: 0 0 15px 0;">
{{ t.base.thanks }}
</p>
<p style="margin: 0; font-size: 11px; color: #999999;">
{{ t.base.footer_reason }} <a href="mailto:{{ t.base.contact_email }}" style="color: #1F4E79; text-decoration: none;">{{ t.base.contact_email }}</a>.
</p>
<p style="margin: 15px 0 0 0; font-size: 11px; color: #999999;">
© {{ year | default('2025') }} {{ t.base.rights }}
</p>
</td>
</tr>
<body style="margin: 0; padding: 0;">

<div style="width: 100%;">
<table role="presentation" style="width: 100%; border-collapse: collapse; border: 0; border-spacing: 0;">
<tr>
<td align="center" style="padding: 40px 0;">

<table role="presentation" style="width: 100%; max-width: 600px; border-collapse: collapse; border: 0; border-spacing: 0;">
<tr>
<td style="padding: 0 0 20px 0; text-align: center;">
<img src="https://ideologicalatlas.com/logo.png" alt="{{ project_name }}" width="48" height="48" style="display: block; margin: 0 auto; border: 0; outline: none; text-decoration: none;">
</td>
</tr>
</table>

<table role="presentation" style="width: 100%; max-width: 600px; border-collapse: separate; border-spacing: 0; background-color: {{ theme.card }}; border-radius: 12px; border: 1px solid {{ theme.border }}; overflow: hidden; box-shadow: 0px 4px 20px rgba(0,0,0,0.25);">
<tr>
<td style="padding: 40px; color: {{ theme.foreground }}; font-size: 16px; line-height: 1.6;">
{% block content %}{% endblock %}
</td>
</tr>

<tr>
<td style="padding: 30px; background-color: {{ theme.secondary }}; border-top: 1px solid {{ theme.border }}; font-size: 13px; line-height: 1.6; color: {{ theme.muted_foreground }}; text-align: center;">
<p style="margin: 0 0 15px 0;">
<strong style="color: {{ theme.foreground }};">{{ t.base.doubts }}</strong><br>
{{ t.base.contact_text }} <a href="mailto:{{ t.base.contact_email }}" style="color: {{ theme.primary }}; font-weight: bold;">{{ t.base.contact_email }}</a> {{ t.base.help_text }}
</p>
<p style="margin: 0 0 15px 0;">
{{ t.base.thanks }}
</p>
<p style="margin: 0; font-size: 12px; color: {{ theme.muted_foreground }};">
{{ t.base.footer_reason }} <a href="mailto:{{ t.base.contact_email }}" style="color: {{ theme.primary }};">{{ t.base.contact_email }}</a>.
</p>
<p style="margin: 15px 0 0 0; font-size: 12px; color: {{ theme.muted_foreground }};">
© {{ year | default('2026') }} {{ t.base.rights }}
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>

<table role="presentation" style="width: 100%; max-width: 600px; border-collapse: collapse; border: 0; border-spacing: 0;">
<tr>
<td style="padding: 20px 0;"></td>
</tr>
</table>

</td>
</tr>
</table>
</div>
</body>
</html>
26 changes: 15 additions & 11 deletions src/templates/register/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@
{% block subject %}{{ t.register.subject }}{% endblock %}

{% block content %}
<h1 style="color: #1F4E79; margin-top: 0;">{{ t.register.title }}</h1>
<p>
<h1 style="color: {{ theme.foreground }}; margin-top: 0; font-size: 24px; font-weight: bold; text-align: center;">{{ t.register.title }}</h1>
<p style="color: #cbd5e1; text-align: center; margin-bottom: 30px;">
{{ t.register.intro|safe }}
</p>
<p>
{{ t.register.verify_text }}
<p>
<p style="text-align: center; padding: 20px 0;">
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="background-color: #1F4E79; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">

<p style="text-align: center; margin-bottom: 30px;">
<a href="{{site_url}}/verify/{{verification_token}}" style="background-color: {{ theme.primary }}; color: {{ theme.primary_foreground }}; padding: 14px 32px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block; font-size: 16px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
{{ t.register.button }}
</a>
</p>
<p>

<p style="font-size: 14px; color: {{ theme.muted_foreground }}; border-top: 1px solid {{ theme.border }}; padding-top: 20px;">
{{ t.register.verify_text }}
</p>

<p style="font-size: 14px; color: {{ theme.muted_foreground }};">
{{ t.register.fallback_text }}<br>
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="color: #1F4E79; word-break: break-all; font-size: 14px;">{{site_url}}/api/users/verify/{{user_uuid}}</a>
<a href="{{site_url}}/verify/{{verification_token}}" style="color: {{ theme.primary }}; word-break: break-all; text-decoration: none;">{{site_url}}/verify/{{verification_token}}</a>
</p>
<p style="margin-top: 30px; border-top: 1px solid #eeeeee; padding-top: 20px;">

<p style="margin-top: 30px; color: {{ theme.foreground }};">
{{ t.register.salutation }}<br>
<strong>{{ t.register.team }}</strong>
<strong style="color: {{ theme.primary }};">{{ t.register.team }}</strong>
</p>
{% endblock %}
14 changes: 8 additions & 6 deletions src/templates/register_google/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
{% block subject %}{{ t.register_google.subject }}{% endblock %}

{% block content %}
<h1 style="color: #1F4E79; margin-top: 0;">{{ t.register_google.title }}</h1>
<p>
<h1 style="color: {{ theme.foreground }}; margin-top: 0; font-size: 24px; font-weight: bold; text-align: center;">{{ t.register_google.title }}</h1>
<p style="color: #cbd5e1; text-align: center; margin-bottom: 30px;">
{{ t.register_google.intro|safe }}
</p>
<p style="text-align: center; padding: 20px 0;">
<a href="{{site_url}}" style="background-color: #1F4E79; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">

<p style="text-align: center; margin-bottom: 30px;">
<a href="{{site_url}}" style="background-color: {{ theme.primary }}; color: {{ theme.primary_foreground }}; padding: 14px 32px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block; font-size: 16px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
{{ t.register_google.button }}
</a>
</p>
<p style="margin-top: 30px; border-top: 1px solid #eeeeee; padding-top: 20px;">

<p style="margin-top: 30px; color: {{ theme.foreground }};">
{{ t.register_google.salutation }}<br>
<strong>{{ t.register_google.team }}</strong>
<strong style="color: {{ theme.primary }};">{{ t.register_google.team }}</strong>
</p>
{% endblock %}
20 changes: 10 additions & 10 deletions src/templates/registration_reminder_30_days/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
{% block subject %}{{ t.registration_reminder_30_days.subject }}{% endblock %}

{% block content %}
<h1 style="color: #D9534F; margin-top: 0;">{{ t.registration_reminder_30_days.title }}</h1>
<p>
<h1 style="color: {{ theme.destructive }}; margin-top: 0; font-size: 24px;">{{ t.registration_reminder_30_days.title }}</h1>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_30_days.intro|safe }}
</p>
<p>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_30_days.verify_text }}
<p>
<p style="text-align: center; padding: 20px 0;">
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="background-color: #D9534F; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
</p>
<p style="text-align: center; margin: 30px 0;">
<a href="{{site_url}}/verify/{{verification_token}}" style="background-color: {{ theme.destructive }}; color: {{ theme.destructive_foreground }}; padding: 12px 25px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block;">
{{ t.registration_reminder_30_days.button }}
</a>
</p>
<p>
<p style="font-size: 14px; color: {{ theme.muted_foreground }};">
{{ t.registration_reminder_30_days.fallback_text }}<br>
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="color: #D9534F; word-break: break-all; font-size: 14px;">{{site_url}}/api/users/verify/{{user_uuid}}</a>
<a href="{{site_url}}/verify/{{verification_token}}" style="color: {{ theme.destructive }}; word-break: break-all;">{{site_url}}/verify/{{verification_token}}</a>
</p>
<p style="margin-top: 30px; border-top: 1px solid #eeeeee; padding-top: 20px;">
<p style="margin-top: 30px; border-top: 1px solid {{ theme.border }}; padding-top: 20px; color: {{ theme.foreground }};">
{{ t.registration_reminder_30_days.salutation }}<br>
<strong>{{ t.registration_reminder_30_days.team }}</strong>
<strong style="color: {{ theme.primary }};">{{ t.registration_reminder_30_days.team }}</strong>
</p>
{% endblock %}
20 changes: 10 additions & 10 deletions src/templates/registration_reminder_3_days/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
{% block subject %}{{ t.registration_reminder_3_days.subject }}{% endblock %}

{% block content %}
<h1 style="color: #1F4E79; margin-top: 0;">{{ t.registration_reminder_3_days.title }}</h1>
<p>
<h1 style="color: {{ theme.foreground }}; margin-top: 0; font-size: 24px;">{{ t.registration_reminder_3_days.title }}</h1>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_3_days.intro|safe }}
</p>
<p>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_3_days.verify_text }}
<p>
<p style="text-align: center; padding: 20px 0;">
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="background-color: #1F4E79; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
</p>
<p style="text-align: center; margin: 30px 0;">
<a href="{{site_url}}/verify/{{verification_token}}" style="background-color: {{ theme.primary }}; color: {{ theme.primary_foreground }}; padding: 12px 25px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block;">
{{ t.registration_reminder_3_days.button }}
</a>
</p>
<p>
<p style="font-size: 14px; color: {{ theme.muted_foreground }};">
{{ t.registration_reminder_3_days.fallback_text }}<br>
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="color: #1F4E79; word-break: break-all; font-size: 14px;">{{site_url}}/api/users/verify/{{user_uuid}}</a>
<a href="{{site_url}}/verify/{{verification_token}}" style="color: {{ theme.primary }}; word-break: break-all;">{{site_url}}/verify/{{verification_token}}</a>
</p>
<p style="margin-top: 30px; border-top: 1px solid #eeeeee; padding-top: 20px;">
<p style="margin-top: 30px; border-top: 1px solid {{ theme.border }}; padding-top: 20px; color: {{ theme.foreground }};">
{{ t.registration_reminder_3_days.salutation }}<br>
<strong>{{ t.registration_reminder_3_days.team }}</strong>
<strong style="color: {{ theme.primary }};">{{ t.registration_reminder_3_days.team }}</strong>
</p>
{% endblock %}
20 changes: 10 additions & 10 deletions src/templates/registration_reminder_7_days/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
{% block subject %}{{ t.registration_reminder_7_days.subject }}{% endblock %}

{% block content %}
<h1 style="color: #1F4E79; margin-top: 0;">{{ t.registration_reminder_7_days.title }}</h1>
<p>
<h1 style="color: {{ theme.foreground }}; margin-top: 0; font-size: 24px;">{{ t.registration_reminder_7_days.title }}</h1>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_7_days.intro|safe }}
</p>
<p>
<p style="color: #cbd5e1;">
{{ t.registration_reminder_7_days.verify_text }}
<p>
<p style="text-align: center; padding: 20px 0;">
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="background-color: #1F4E79; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
</p>
<p style="text-align: center; margin: 30px 0;">
<a href="{{site_url}}/verify/{{verification_token}}" style="background-color: {{ theme.primary }}; color: {{ theme.primary_foreground }}; padding: 12px 25px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block;">
{{ t.registration_reminder_7_days.button }}
</a>
</p>
<p>
<p style="font-size: 14px; color: {{ theme.muted_foreground }};">
{{ t.registration_reminder_7_days.fallback_text }}<br>
<a href="{{site_url}}/api/users/verify/{{user_uuid}}" style="color: #1F4E79; word-break: break-all; font-size: 14px;">{{site_url}}/api/users/verify/{{user_uuid}}</a>
<a href="{{site_url}}/verify/{{verification_token}}" style="color: {{ theme.primary }}; word-break: break-all;">{{site_url}}/verify/{{verification_token}}</a>
</p>
<p style="margin-top: 30px; border-top: 1px solid #eeeeee; padding-top: 20px;">
<p style="margin-top: 30px; border-top: 1px solid {{ theme.border }}; padding-top: 20px; color: {{ theme.foreground }};">
{{ t.registration_reminder_7_days.salutation }}<br>
<strong>{{ t.registration_reminder_7_days.team }}</strong>
<strong style="color: {{ theme.primary }};">{{ t.registration_reminder_7_days.team }}</strong>
</p>
{% endblock %}
Loading