## Summary - Closes #26 - Shared dark branded email base (`emails/base_email.html`) matching site tokens: `#0a0a0a` / `#1a1a1a`, cyan `#00f3ff`, purple `#bc13fe`, logo, Inter-safe stack - Refactors contact, marketing (+ preview), and invoice pay-link HTML/text to extend that base; drops Materialize CDN - Adds `PUBLIC_SITE_URL` + `email_brand_context()` for absolute logo/footer links ## Email renders ### Contact  ### Marketing  ### Invoice pay link  Also see `docs/email-previews/`. ## Test plan - [ ] Preview a marketing `EmailMessage` in admin / `/preview_email/<pk>/` — dark brand, logo, no Materialize - [ ] Submit contact form (or render `emails/contact_email.html`) — subject/from/message fields + branded footer - [ ] Send (or dry-render) invoice pay-link email — cyan CTA, amount accent - [ ] Confirm logo loads from `PUBLIC_SITE_URL/static/public/img/logo.png` in a real clientReviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
@@ -34,6 +34,9 @@ EMAIL_HOST_PASSWORD=
|
|||||||
EMAIL_PORT=2525
|
EMAIL_PORT=2525
|
||||||
EMAIL_USE_TLS=true
|
EMAIL_USE_TLS=true
|
||||||
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
|
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
|
||||||
|
# Absolute origin for email logo / footer links
|
||||||
|
PUBLIC_SITE_URL=https://aimloperations.com
|
||||||
|
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
|
||||||
|
|
||||||
# Stripe (test keys from https://dashboard.stripe.com/test/apikeys)
|
# Stripe (test keys from https://dashboard.stripe.com/test/apikeys)
|
||||||
STRIPE_SECRET_KEY=
|
STRIPE_SECRET_KEY=
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ EMAIL_HOST_PASSWORD=replace-with-smtp-password
|
|||||||
EMAIL_PORT=2525
|
EMAIL_PORT=2525
|
||||||
EMAIL_USE_TLS=true
|
EMAIL_USE_TLS=true
|
||||||
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
|
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
|
||||||
|
PUBLIC_SITE_URL=https://aimloperations.com
|
||||||
|
|
||||||
# Stripe (live keys from https://dashboard.stripe.com/apikeys)
|
# Stripe (live keys from https://dashboard.stripe.com/apikeys)
|
||||||
# Secret key starts with sk_live_; publishable with pk_live_
|
# Secret key starts with sk_live_; publishable with pk_live_
|
||||||
|
|||||||
@@ -204,6 +204,8 @@ DEFAULT_FROM_EMAIL = env(
|
|||||||
"DEFAULT_FROM_EMAIL",
|
"DEFAULT_FROM_EMAIL",
|
||||||
"AI ML Operations, LLC <info@aimloperations.com>",
|
"AI ML Operations, LLC <info@aimloperations.com>",
|
||||||
)
|
)
|
||||||
|
# Absolute site origin for email logo / footer links (no trailing slash).
|
||||||
|
PUBLIC_SITE_URL = env("PUBLIC_SITE_URL", "https://aimloperations.com").rstrip("/")
|
||||||
|
|
||||||
# Stripe (invoices / subscriptions — customers pay us)
|
# Stripe (invoices / subscriptions — customers pay us)
|
||||||
STRIPE_SECRET_KEY = env("STRIPE_SECRET_KEY", "")
|
STRIPE_SECRET_KEY = env("STRIPE_SECRET_KEY", "")
|
||||||
|
|||||||
@@ -284,14 +284,16 @@ def send_pay_link_email(
|
|||||||
"DEFAULT_FROM_EMAIL",
|
"DEFAULT_FROM_EMAIL",
|
||||||
"AI ML Operations, LLC <info@aimloperations.com>",
|
"AI ML Operations, LLC <info@aimloperations.com>",
|
||||||
)
|
)
|
||||||
context = {
|
from public.email_branding import email_brand_context
|
||||||
"customer_name": customer_name,
|
|
||||||
"description": description,
|
context = email_brand_context(
|
||||||
"amount_dollars": f"{amount_dollars:.2f}",
|
customer_name=customer_name,
|
||||||
"pay_url": pay_url,
|
description=description,
|
||||||
"kind": kind,
|
amount_dollars=f"{amount_dollars:.2f}",
|
||||||
"subject": subject,
|
pay_url=pay_url,
|
||||||
}
|
kind=kind,
|
||||||
|
subject=subject,
|
||||||
|
)
|
||||||
html_content = get_template("emails/invoice_pay_link.html").render(context)
|
html_content = get_template("emails/invoice_pay_link.html").render(context)
|
||||||
text_content = get_template("emails/invoice_pay_link.txt").render(context)
|
text_content = get_template("emails/invoice_pay_link.txt").render(context)
|
||||||
msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email])
|
msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email])
|
||||||
|
|||||||
@@ -1,48 +1,26 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "emails/base_email.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}{{ subject }}{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% block content %}
|
||||||
<title>{{ subject }}</title>
|
<p style="margin:0 0 16px;color:#e0e0e0;">Hello {{ customer_name }},</p>
|
||||||
<style>
|
{% if kind == "subscription" %}
|
||||||
body { margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4; }
|
<p style="margin:0 0 16px;color:#e0e0e0;">Please complete checkout to start your recurring payment:</p>
|
||||||
.email-container { max-width: 600px; margin: 0 auto; background-color: #ffffff; border: 1px solid #dddddd; }
|
{% else %}
|
||||||
.header { background-color: #0b3d5c; color: #ffffff; padding: 20px; text-align: center; }
|
<p style="margin:0 0 16px;color:#e0e0e0;">You have a new invoice ready for payment:</p>
|
||||||
.content { padding: 24px; color: #333333; font-size: 15px; line-height: 1.5; }
|
{% endif %}
|
||||||
.btn {
|
<p style="margin:0 0 8px;color:#e0e0e0;"><strong>{{ description }}</strong></p>
|
||||||
display: inline-block; margin: 20px 0; padding: 12px 24px;
|
<p class="amount" style="font-size:22px;font-weight:700;color:#00f3ff;margin:12px 0;">${{ amount_dollars }} USD</p>
|
||||||
background-color: #0b3d5c; color: #ffffff !important; text-decoration: none;
|
<p style="margin:24px 0;">
|
||||||
border-radius: 4px; font-weight: bold;
|
<a class="email-btn" href="{{ pay_url }}" style="display:inline-block;padding:14px 28px;background-color:#00f3ff;color:#0a0a0a !important;text-decoration:none;border-radius:30px;font-weight:700;font-size:14px;letter-spacing:0.5px;text-transform:uppercase;">
|
||||||
}
|
|
||||||
.footer { background-color: #f4f4f4; color: #777777; text-align: center; padding: 12px; font-size: 12px; }
|
|
||||||
.amount { font-size: 22px; font-weight: bold; margin: 12px 0; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="email-container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>AI ML Operations</h1>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>Hello {{ customer_name }},</p>
|
|
||||||
{% if kind == "subscription" %}
|
|
||||||
<p>Please complete checkout to start your recurring payment:</p>
|
|
||||||
{% else %}
|
|
||||||
<p>You have a new invoice ready for payment:</p>
|
|
||||||
{% endif %}
|
|
||||||
<p><strong>{{ description }}</strong></p>
|
|
||||||
<p class="amount">${{ amount_dollars }} USD</p>
|
|
||||||
<p>
|
|
||||||
<a class="btn" href="{{ pay_url }}">
|
|
||||||
{% if kind == "subscription" %}Complete checkout{% else %}Pay invoice{% endif %}
|
{% if kind == "subscription" %}Complete checkout{% else %}Pay invoice{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p style="font-size: 13px; color: #666;">Or open this link:<br>{{ pay_url }}</p>
|
<p class="muted" style="color:#a0a0a0;font-size:13px;line-height:1.5;margin:0 0 16px;">
|
||||||
<p>Thank you,<br>AI ML Operations, LLC</p>
|
Or open this link:<br>
|
||||||
</div>
|
<a href="{{ pay_url }}" style="color:#00f3ff;word-break:break-all;">{{ pay_url }}</a>
|
||||||
<div class="footer">
|
</p>
|
||||||
<p>This is an automated message. Please do not reply to this email.</p>
|
<p style="margin:0;color:#e0e0e0;">Thank you,<br>{{ brand_legal|default:"AI ML Operations, LLC" }}</p>
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
|
||||||
</body>
|
{% block footer_note %}This is an automated message. Please do not reply to this email.{% endblock %}
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ Amount: ${{ amount_dollars }} USD
|
|||||||
{{ pay_url }}
|
{{ pay_url }}
|
||||||
|
|
||||||
Thank you,
|
Thank you,
|
||||||
AI ML Operations, LLC
|
{{ brand_legal|default:"AI ML Operations, LLC" }}
|
||||||
|
|
||||||
|
—
|
||||||
|
{{ site_url|default:"https://aimloperations.com" }}
|
||||||
|
Forward-deployed AI engineering
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@@ -5,6 +6,7 @@ from django.template.loader import get_template
|
|||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from .email_branding import email_brand_context
|
||||||
from .models import Contact, EmailMessage, PageVisit
|
from .models import Contact, EmailMessage, PageVisit
|
||||||
|
|
||||||
|
|
||||||
@@ -36,8 +38,12 @@ def send_emails(modeladmin, request, queryset):
|
|||||||
for email in queryset:
|
for email in queryset:
|
||||||
success_count: int = 0
|
success_count: int = 0
|
||||||
try:
|
try:
|
||||||
from_email = "AI ML Operations, LLC <info@aimloperations.com>"
|
from_email = getattr(
|
||||||
d = {"title": email.subject, "content": email.body}
|
settings,
|
||||||
|
"DEFAULT_FROM_EMAIL",
|
||||||
|
"AI ML Operations, LLC <info@aimloperations.com>",
|
||||||
|
)
|
||||||
|
d = email_brand_context(title=email.subject, content=email.body)
|
||||||
|
|
||||||
html_content = get_template("emails/marketing_email.html").render(d)
|
html_content = get_template("emails/marketing_email.html").render(d)
|
||||||
text_content = get_template("emails/marketing_email.txt").render(d)
|
text_content = get_template("emails/marketing_email.txt").render(d)
|
||||||
@@ -77,8 +83,11 @@ class EmailMessageAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
def preview_email(self, request, pk):
|
def preview_email(self, request, pk):
|
||||||
email_instance = get_object_or_404(EmailMessage, pk=pk)
|
email_instance = get_object_or_404(EmailMessage, pk=pk)
|
||||||
context = {"title": email_instance.subject, "content": email_instance.body}
|
context = email_brand_context(
|
||||||
return TemplateResponse(request, "public/preview_email.html", context)
|
title=email_instance.subject,
|
||||||
|
content=email_instance.body,
|
||||||
|
)
|
||||||
|
return TemplateResponse(request, "emails/marketing_email.html", context)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(PageVisit)
|
@admin.register(PageVisit)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"""Shared context for branded HTML/text emails."""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
|
||||||
|
|
||||||
|
def email_brand_context(**extra):
|
||||||
|
site_url = getattr(settings, "PUBLIC_SITE_URL", "https://aimloperations.com").rstrip(
|
||||||
|
"/"
|
||||||
|
)
|
||||||
|
logo_path = staticfiles_storage.url("public/img/logo.png")
|
||||||
|
if logo_path.startswith("http://") or logo_path.startswith("https://"):
|
||||||
|
logo_url = logo_path
|
||||||
|
else:
|
||||||
|
logo_url = f"{site_url}{logo_path}"
|
||||||
|
return {
|
||||||
|
"site_url": site_url,
|
||||||
|
"logo_url": logo_url,
|
||||||
|
"brand_name": "AI ML Operations",
|
||||||
|
"brand_legal": "AI ML Operations, LLC",
|
||||||
|
**extra,
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="color-scheme" content="dark">
|
||||||
|
<meta name="supported-color-schemes" content="dark">
|
||||||
|
<title>{% block title %}{{ brand_name|default:"AI ML Operations" }}{% endblock %}</title>
|
||||||
|
<!--[if mso]>
|
||||||
|
<style type="text/css">
|
||||||
|
body, table, td { font-family: Arial, Helvetica, sans-serif !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<style type="text/css">
|
||||||
|
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 {
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
background-color: #0a0a0a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
a { color: #00f3ff; }
|
||||||
|
.email-btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 14px 28px;
|
||||||
|
background-color: #00f3ff;
|
||||||
|
color: #0a0a0a !important;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.muted { color: #a0a0a0; font-size: 13px; line-height: 1.5; }
|
||||||
|
.amount { font-size: 22px; font-weight: 700; color: #00f3ff; margin: 12px 0; }
|
||||||
|
.field-label { color: #a0a0a0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.6px; margin: 0 0 4px; }
|
||||||
|
.field-value { color: #e0e0e0; font-size: 15px; margin: 0 0 16px; line-height: 1.5; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background-color:#0a0a0a;">
|
||||||
|
{% block preheader %}{% endblock %}
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color:#0a0a0a;">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding:32px 16px;">
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="max-width:600px;background-color:#1a1a1a;border:1px solid rgba(255,255,255,0.1);">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding:28px 24px 20px;border-bottom:1px solid rgba(0,243,255,0.35);">
|
||||||
|
{% if logo_url %}
|
||||||
|
<a href="{{ site_url|default:'https://aimloperations.com' }}" style="text-decoration:none;">
|
||||||
|
<img src="{{ logo_url }}" alt="{{ brand_name|default:'AI ML Operations' }}" width="220" style="display:block;width:220px;max-width:80%;height:auto;">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<p style="margin:0;font-size:18px;font-weight:700;letter-spacing:1px;color:#e0e0e0;text-transform:uppercase;">
|
||||||
|
{{ brand_name|default:"AI ML Operations" }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% block header_extra %}{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="height:3px;line-height:3px;font-size:0;background-color:#bc13fe;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:28px 28px 8px;color:#e0e0e0;font-size:15px;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px 28px 28px;color:#a0a0a0;font-size:12px;line-height:1.5;text-align:center;border-top:1px solid rgba(255,255,255,0.08);font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
||||||
|
{% block footer %}
|
||||||
|
<p style="margin:16px 0 8px;color:#a0a0a0;">
|
||||||
|
{% block footer_note %}{% endblock %}
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 4px;color:#a0a0a0;">
|
||||||
|
© {% now "Y" %} {{ brand_legal|default:"AI ML Operations, LLC" }}. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<p style="margin:0;color:#a0a0a0;">
|
||||||
|
<a href="{{ site_url|default:'https://aimloperations.com' }}" style="color:#00f3ff;text-decoration:none;">aimloperations.com</a>
|
||||||
|
· Forward-deployed AI engineering
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,110 +1,21 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "emails/base_email.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>New Feedback Submission</title>
|
|
||||||
<style>
|
|
||||||
/* Basic reset for email clients */
|
|
||||||
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 {
|
|
||||||
border: 0;
|
|
||||||
height: auto;
|
|
||||||
line-height: 100%;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
-ms-interpolation-mode: bicubic;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
.email-container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #dddddd;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #007BFF;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
color: #777777;
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.feedback-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.feedback-text {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" align="center">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<!-- Email Container -->
|
|
||||||
<div class="email-container">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="header">
|
|
||||||
<h1>New Contact Request</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
{% block title %}New Contact Request{% endblock %}
|
||||||
<div class="content">
|
|
||||||
<p>Hello,</p>
|
|
||||||
<p>A new feedback item has been submitted. Here are the details:</p>
|
|
||||||
|
|
||||||
<!-- Feedback Title -->
|
{% block content %}
|
||||||
<div class="feedback-title">
|
<p style="margin:0 0 16px;color:#e0e0e0;">Hello,</p>
|
||||||
Subject: <strong>{{ subject }}</strong>
|
<p style="margin:0 0 24px;color:#e0e0e0;">A new contact request was submitted on the site.</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email -->
|
<p class="field-label" style="color:#a0a0a0;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Subject</p>
|
||||||
<div class="feedback-title">
|
<p class="field-value" style="color:#e0e0e0;font-size:15px;margin:0 0 16px;"><strong>{{ subject }}</strong></p>
|
||||||
Email: <strong>{{ email }}</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Feedback Text -->
|
<p class="field-label" style="color:#a0a0a0;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">From</p>
|
||||||
<div class="feedback-text">
|
<p class="field-value" style="color:#e0e0e0;font-size:15px;margin:0 0 16px;">
|
||||||
<strong>Message:</strong><br>
|
<a href="mailto:{{ email }}" style="color:#00f3ff;text-decoration:none;">{{ email }}</a>
|
||||||
{{ message }}
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>Thank you for your attention.</p>
|
<p class="field-label" style="color:#a0a0a0;font-size:12px;text-transform:uppercase;letter-spacing:0.6px;margin:0 0 4px;">Message</p>
|
||||||
</div>
|
<p class="field-value" style="color:#e0e0e0;font-size:15px;margin:0 0 16px;white-space:pre-wrap;">{{ message }}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Footer -->
|
{% block footer_note %}This is an automated message. Please do not reply to this email.{% endblock %}
|
||||||
<div class="footer">
|
|
||||||
<p>This is an automated message. Please do not reply to this email.</p>
|
|
||||||
<p>© 2025 AI ML Operations, LLC. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
New Contact Request for AI ML Operations, LLC
|
New Contact Request — {{ brand_legal|default:"AI ML Operations, LLC" }}
|
||||||
|
|
||||||
"New Contact. {{ subject }} from {{ email }}. {{ message }}"
|
Subject: {{ subject }}
|
||||||
|
From: {{ email }}
|
||||||
|
|
||||||
|
Message:
|
||||||
|
{{ message }}
|
||||||
|
|
||||||
|
—
|
||||||
|
{{ brand_legal|default:"AI ML Operations, LLC" }}
|
||||||
|
{{ site_url|default:"https://aimloperations.com" }}
|
||||||
|
Forward-deployed AI engineering
|
||||||
|
|||||||
@@ -1,61 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "emails/base_email.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Email Template</title>
|
|
||||||
<!-- Materialize CSS -->
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.email-container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #37474f;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
background-color: #333;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="email-container">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="header">
|
|
||||||
<h4>{{ title | safe }}</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
{% block title %}{{ title }}{% endblock %}
|
||||||
<div class="content">
|
|
||||||
{{ content | safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
{% block header_extra %}
|
||||||
<div class="footer">
|
{% if title %}
|
||||||
<p>© 2025 AI ML Operations, LLC. All rights reserved.</p>
|
<p style="margin:16px 0 0;font-size:18px;font-weight:600;color:#e0e0e0;line-height:1.4;">{{ title|safe }}</p>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
{% block content %}
|
||||||
|
{{ content|safe }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer_note %}{% endblock %}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
{{ subject }}
|
{{ title }}
|
||||||
|
|
||||||
{{ content }}
|
{{ content }}
|
||||||
|
|
||||||
|
—
|
||||||
|
{{ brand_legal|default:"AI ML Operations, LLC" }}
|
||||||
|
{{ site_url|default:"https://aimloperations.com" }}
|
||||||
|
Forward-deployed AI engineering
|
||||||
|
|||||||
@@ -1,61 +1 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "emails/marketing_email.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Email Template</title>
|
|
||||||
<!-- Materialize CSS -->
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.email-container {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #37474f;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
background-color: #333;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="email-container">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="header">
|
|
||||||
<h4>{{ title | safe }}</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div class="content">
|
|
||||||
{{ content | safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<div class="footer">
|
|
||||||
<p>© 2025 AI ML Operations, LLC. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -14,20 +14,25 @@ from django.urls import reverse
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
from .email_branding import email_brand_context
|
||||||
from .forms import FormWithCaptcha
|
from .forms import FormWithCaptcha
|
||||||
from .middleware import get_session_utm
|
from .middleware import get_session_utm
|
||||||
from .models import Contact, EmailMessage, PageVisit
|
from .models import Contact, EmailMessage, PageVisit
|
||||||
from .traffic import TRAFFIC_TYPE_LABELS, TrafficType
|
from .traffic import TRAFFIC_TYPE_LABELS, TrafficType
|
||||||
|
|
||||||
def send_contact_email(email, subject, message):
|
def send_contact_email(email, subject, message):
|
||||||
subject = "New Contact Request for AI ML Operations, LLC"
|
mail_subject = "New Contact Request for AI ML Operations, LLC"
|
||||||
from_email = "ryan@aimloperations.com"
|
from_email = getattr(
|
||||||
to="ryan@aimloperations.com"
|
settings,
|
||||||
d = {"subject": subject, "message": message, "email": email}
|
"DEFAULT_FROM_EMAIL",
|
||||||
html_content = get_template(r'emails/contact_email.html').render(d)
|
"AI ML Operations, LLC <info@aimloperations.com>",
|
||||||
text_content = get_template(r'emails/contact_email.txt').render(d)
|
)
|
||||||
|
to = "ryan@aimloperations.com"
|
||||||
|
d = email_brand_context(subject=subject, message=message, email=email)
|
||||||
|
html_content = get_template("emails/contact_email.html").render(d)
|
||||||
|
text_content = get_template("emails/contact_email.txt").render(d)
|
||||||
|
|
||||||
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
msg = EmailMultiAlternatives(mail_subject, text_content, from_email, [to])
|
||||||
msg.attach_alternative(html_content, "text/html")
|
msg.attach_alternative(html_content, "text/html")
|
||||||
msg.send(fail_silently=True)
|
msg.send(fail_silently=True)
|
||||||
|
|
||||||
@@ -81,13 +86,11 @@ def terms_of_service(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def preview_email(request, pk):
|
def preview_email(request, pk):
|
||||||
email_instance = get_object_or_404(EmailMessage, pk=pk)
|
email_instance = get_object_or_404(EmailMessage, pk=pk)
|
||||||
context = {
|
context = email_brand_context(
|
||||||
"title":email_instance.subject,
|
title=email_instance.subject,
|
||||||
"content":email_instance.body
|
content=email_instance.body,
|
||||||
}
|
|
||||||
return render(
|
|
||||||
request, 'public/preview_email.html', context
|
|
||||||
)
|
)
|
||||||
|
return render(request, "emails/marketing_email.html", context)
|
||||||
|
|
||||||
def contact(request):
|
def contact(request):
|
||||||
errors = ''
|
errors = ''
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Email previews
|
||||||
|
|
||||||
|
Static renders of branded HTML emails (dark neon site UX: `#0a0a0a` / `#1a1a1a` / `#00f3ff` / `#bc13fe`).
|
||||||
|
|
||||||
|
| Template | Screenshot |
|
||||||
|
|----------|------------|
|
||||||
|
| Contact notify | [contact.png](contact.png) |
|
||||||
|
| Marketing | [marketing.png](marketing.png) |
|
||||||
|
| Invoice / pay link | [invoice.png](invoice.png) |
|
||||||
|
|
||||||
|
Shared base: `public/templates/emails/base_email.html`.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Reference in New Issue
Block a user