Mail looks 'improved'
Build LinkLog Development Image / development-image (push) Successful in 19s

This commit is contained in:
2026-08-26 20:27:54 +02:00
parent 314959c7bf
commit b03a241be2
4 changed files with 144 additions and 4 deletions
+60 -2
View File
@@ -2,13 +2,57 @@
## SPDX-License-Identifier: GPL-3.0-or-later
from email.message import EmailMessage
from html import escape
from pathlib import Path
from smtplib import SMTP
import json
from urllib.parse import urlparse
from backend.app.core.config import settings
from backend.app.database import get_connection
from backend.app.services.secret_store import decrypt_secret, encrypt_secret
LOGO_PATH = Path(__file__).resolve().parents[3] / 'frontend' / 'static' / 'logo.svg'
def _html_email(body_html: str) -> str:
public_url = settings.public_url
parsed_url = urlparse(public_url)
public_hostname = parsed_url.hostname or public_url
safe_public_url = escape(public_url, quote=True)
safe_public_hostname = escape(public_hostname)
return f'''<!doctype html>
<html lang="en">
<body style="margin:0;background:#1e1e2e;color:#cdd6f4;font-family:Arial,sans-serif;line-height:1.6;">
<div style="max-width:620px;margin:32px auto;padding:0 20px;">
<div style="background:#11111b;border:1px solid #45475a;border-radius:12px;overflow:hidden;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#181825;">
<tr>
<td style="padding:20px 24px;text-align:left;vertical-align:top;width:50px;">
<img src="cid:linklog-logo" alt="LinkLog" width="50" height="50" style="display:block;width:50px;height:50px;">
</td>
<td style="padding:20px 0 20px 12px;text-align:left;vertical-align:top;">
<span style="color:#cba6f7;font-family:'Asset',Georgia,serif;font-size:18px;line-height:50px;">Hello, a message from <a href="{safe_public_url}" style="color:#cba6f7;text-decoration:underline;">{safe_public_hostname}</a></span>
</td>
</tr>
</table>
<div style="padding:28px 32px;">{body_html}</div>
</div>
<p style="margin:18px 0;text-align:center;color:#a6adc8;font-size:12px;">LinkLog</p>
</div>
</body>
</html>'''
def _add_html_body(message: EmailMessage, html_body: str) -> None:
message.add_alternative(_html_email(html_body), subtype='html')
html_part = message.get_payload()[-1]
try:
logo = LOGO_PATH.read_bytes()
except OSError:
return
html_part.add_related(logo, maintype='image', subtype='svg+xml', cid='<linklog-logo>')
def get_smtp_settings() -> dict:
values = {
@@ -42,7 +86,8 @@ def smtp_configured(smtp_values: dict | None = None) -> bool:
return bool(smtp['smtp_host'] and smtp['smtp_from'])
def send_message(email: str, subject: str, body: str, smtp_values: dict | None = None) -> None:
def send_message(email: str, subject: str, body: str, html_body: str | None = None,
smtp_values: dict | None = None) -> None:
smtp = smtp_values or get_smtp_settings()
if not smtp_configured(smtp):
raise RuntimeError('SMTP is not configured; set LINKLOG_SMTP_HOST and LINKLOG_SMTP_FROM')
@@ -52,6 +97,8 @@ def send_message(email: str, subject: str, body: str, smtp_values: dict | None =
message['From'] = smtp['smtp_from']
message['To'] = email
message.set_content(body)
if html_body:
_add_html_body(message, html_body)
with SMTP(smtp['smtp_host'], smtp['smtp_port'], timeout=10) as connection:
if smtp['smtp_use_tls']:
@@ -62,12 +109,17 @@ def send_message(email: str, subject: str, body: str, smtp_values: dict | None =
def send_verification_email(email: str, username: str, verification_url: str) -> None:
safe_username = escape(username)
safe_url = escape(verification_url, quote=True)
send_message(
email,
'Verify your LinkLog email address',
f'Hello {username},\n\n'
f'Verify your LinkLog email address by opening this link:\n{verification_url}\n\n'
f'This link expires in {settings.email_verification_expiry_hours} hours.\n',
f'<p>Hello {safe_username},</p><p>Verify your LinkLog email address:</p>'
f'<p><a href="{safe_url}" style="display:inline-block;padding:10px 16px;background:#89b4fa;color:#11111b;text-decoration:none;border-radius:6px;">Verify email address</a></p>'
f'<p style="color:#a6adc8;font-size:14px;">This link expires in {settings.email_verification_expiry_hours} hours.</p>',
)
@@ -76,15 +128,21 @@ def send_test_email(email: str, smtp_values: dict | None = None) -> None:
email,
'LinkLog SMTP test',
'This is a test message from LinkLog. SMTP is configured correctly.\n',
smtp_values,
'<p>This is a test message from LinkLog.</p><p style="color:#a6adc8;">SMTP is configured correctly.</p>',
smtp_values=smtp_values,
)
def send_password_reset_email(email: str, username: str, reset_url: str) -> None:
safe_username = escape(username)
safe_url = escape(reset_url, quote=True)
send_message(
email,
'Reset your LinkLog password',
f'Hello {username},\n\n'
f'Reset your LinkLog password by opening this link:\n{reset_url}\n\n'
f'This link expires in {settings.password_reset_expiry_hours} hours.\n',
f'<p>Hello {safe_username},</p><p>Reset your LinkLog password:</p>'
f'<p><a href="{safe_url}" style="display:inline-block;padding:10px 16px;background:#f38ba8;color:#11111b;text-decoration:none;border-radius:6px;">Reset password</a></p>'
f'<p style="color:#a6adc8;font-size:14px;">This link expires in {settings.password_reset_expiry_hours} hours.</p>',
)