diff --git a/.env.example b/.env.example index 3783686..c265784 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,9 @@ EMAIL_HOST_PASSWORD= EMAIL_PORT=2525 EMAIL_USE_TLS=true # DEFAULT_FROM_EMAIL=AI ML Operations, LLC +# Absolute origin for email logo / footer links +PUBLIC_SITE_URL=https://aimloperations.com +# DEFAULT_FROM_EMAIL=AI ML Operations, LLC # Stripe (test keys from https://dashboard.stripe.com/test/apikeys) STRIPE_SECRET_KEY= diff --git a/.env.prod.example b/.env.prod.example index 5cfa22f..7f36165 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -35,6 +35,7 @@ EMAIL_HOST_PASSWORD=replace-with-smtp-password EMAIL_PORT=2525 EMAIL_USE_TLS=true # DEFAULT_FROM_EMAIL=AI ML Operations, LLC +PUBLIC_SITE_URL=https://aimloperations.com # Stripe (live keys from https://dashboard.stripe.com/apikeys) # Secret key starts with sk_live_; publishable with pk_live_ diff --git a/company_site/company_site/settings/base.py b/company_site/company_site/settings/base.py index 07b8178..e9bc973 100644 --- a/company_site/company_site/settings/base.py +++ b/company_site/company_site/settings/base.py @@ -204,6 +204,8 @@ DEFAULT_FROM_EMAIL = env( "DEFAULT_FROM_EMAIL", "AI ML Operations, LLC ", ) +# 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_SECRET_KEY = env("STRIPE_SECRET_KEY", "") diff --git a/company_site/financial/stripe_billing.py b/company_site/financial/stripe_billing.py index c41e439..0803a02 100644 --- a/company_site/financial/stripe_billing.py +++ b/company_site/financial/stripe_billing.py @@ -284,14 +284,16 @@ def send_pay_link_email( "DEFAULT_FROM_EMAIL", "AI ML Operations, LLC ", ) - context = { - "customer_name": customer_name, - "description": description, - "amount_dollars": f"{amount_dollars:.2f}", - "pay_url": pay_url, - "kind": kind, - "subject": subject, - } + from public.email_branding import email_brand_context + + context = email_brand_context( + customer_name=customer_name, + description=description, + amount_dollars=f"{amount_dollars:.2f}", + pay_url=pay_url, + kind=kind, + subject=subject, + ) html_content = get_template("emails/invoice_pay_link.html").render(context) text_content = get_template("emails/invoice_pay_link.txt").render(context) msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email]) diff --git a/company_site/financial/templates/emails/invoice_pay_link.html b/company_site/financial/templates/emails/invoice_pay_link.html index 9ac8b0c..6f7804e 100644 --- a/company_site/financial/templates/emails/invoice_pay_link.html +++ b/company_site/financial/templates/emails/invoice_pay_link.html @@ -1,48 +1,26 @@ - - - - - - {{ subject }} - - - - - - +{% extends "emails/base_email.html" %} + +{% block title %}{{ subject }}{% endblock %} + +{% block content %} +

Hello {{ customer_name }},

+{% if kind == "subscription" %} +

Please complete checkout to start your recurring payment:

+{% else %} +

You have a new invoice ready for payment:

+{% endif %} +

{{ description }}

+

${{ amount_dollars }} USD

+

+ +

+

+ Or open this link:
+ {{ pay_url }} +

+

Thank you,
{{ brand_legal|default:"AI ML Operations, LLC" }}

+{% endblock %} + +{% block footer_note %}This is an automated message. Please do not reply to this email.{% endblock %} diff --git a/company_site/financial/templates/emails/invoice_pay_link.txt b/company_site/financial/templates/emails/invoice_pay_link.txt index 3d7dace..a44e915 100644 --- a/company_site/financial/templates/emails/invoice_pay_link.txt +++ b/company_site/financial/templates/emails/invoice_pay_link.txt @@ -9,4 +9,8 @@ Amount: ${{ amount_dollars }} USD {{ pay_url }} Thank you, -AI ML Operations, LLC +{{ brand_legal|default:"AI ML Operations, LLC" }} + +— +{{ site_url|default:"https://aimloperations.com" }} +Forward-deployed AI engineering diff --git a/company_site/public/admin.py b/company_site/public/admin.py index 18ce860..1ee7a46 100644 --- a/company_site/public/admin.py +++ b/company_site/public/admin.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib import admin from django.core.mail import EmailMultiAlternatives 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.urls import path +from .email_branding import email_brand_context from .models import Contact, EmailMessage, PageVisit @@ -36,8 +38,12 @@ def send_emails(modeladmin, request, queryset): for email in queryset: success_count: int = 0 try: - from_email = "AI ML Operations, LLC " - d = {"title": email.subject, "content": email.body} + from_email = getattr( + settings, + "DEFAULT_FROM_EMAIL", + "AI ML Operations, LLC ", + ) + d = email_brand_context(title=email.subject, content=email.body) html_content = get_template("emails/marketing_email.html").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): email_instance = get_object_or_404(EmailMessage, pk=pk) - context = {"title": email_instance.subject, "content": email_instance.body} - return TemplateResponse(request, "public/preview_email.html", context) + context = email_brand_context( + title=email_instance.subject, + content=email_instance.body, + ) + return TemplateResponse(request, "emails/marketing_email.html", context) @admin.register(PageVisit) diff --git a/company_site/public/email_branding.py b/company_site/public/email_branding.py new file mode 100644 index 0000000..cd716ed --- /dev/null +++ b/company_site/public/email_branding.py @@ -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, + } diff --git a/company_site/public/templates/emails/base_email.html b/company_site/public/templates/emails/base_email.html new file mode 100644 index 0000000..b7dd9f9 --- /dev/null +++ b/company_site/public/templates/emails/base_email.html @@ -0,0 +1,107 @@ + + + + + + + + {% block title %}{{ brand_name|default:"AI ML Operations" }}{% endblock %} + + + + + {% block preheader %}{% endblock %} + + + + +
+ + + + + + + + + + + + + +
+ {% if logo_url %} + + {{ brand_name|default:'AI ML Operations' }} + + {% else %} +

+ {{ brand_name|default:"AI ML Operations" }} +

+ {% endif %} + {% block header_extra %}{% endblock %} +
 
+ {% block content %}{% endblock %} +
+ {% block footer %} +

+ {% block footer_note %}{% endblock %} +

+

+ © {% now "Y" %} {{ brand_legal|default:"AI ML Operations, LLC" }}. All rights reserved. +

+

+ aimloperations.com +  ·  Forward-deployed AI engineering +

+ {% endblock %} +
+
+ + diff --git a/company_site/public/templates/emails/contact_email.html b/company_site/public/templates/emails/contact_email.html index 14d300c..e261dd5 100644 --- a/company_site/public/templates/emails/contact_email.html +++ b/company_site/public/templates/emails/contact_email.html @@ -1,110 +1,21 @@ - - - - - - New Feedback Submission - - - - - - - -
- - -
- - \ No newline at end of file +{% block footer_note %}This is an automated message. Please do not reply to this email.{% endblock %} diff --git a/company_site/public/templates/emails/contact_email.txt b/company_site/public/templates/emails/contact_email.txt index 13181b9..5060cff 100644 --- a/company_site/public/templates/emails/contact_email.txt +++ b/company_site/public/templates/emails/contact_email.txt @@ -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 }}" \ No newline at end of file +Subject: {{ subject }} +From: {{ email }} + +Message: +{{ message }} + +— +{{ brand_legal|default:"AI ML Operations, LLC" }} +{{ site_url|default:"https://aimloperations.com" }} +Forward-deployed AI engineering diff --git a/company_site/public/templates/emails/marketing_email.html b/company_site/public/templates/emails/marketing_email.html index 0db0636..54b210c 100644 --- a/company_site/public/templates/emails/marketing_email.html +++ b/company_site/public/templates/emails/marketing_email.html @@ -1,61 +1,15 @@ - - - - - - Email Template - - - - - - - - \ No newline at end of file +{% block header_extra %} +{% if title %} +

{{ title|safe }}

+{% endif %} +{% endblock %} + +{% block content %} +{{ content|safe }} +{% endblock %} + +{% block footer_note %}{% endblock %} diff --git a/company_site/public/templates/emails/marketing_email.txt b/company_site/public/templates/emails/marketing_email.txt index ce6ad2d..a8195f8 100644 --- a/company_site/public/templates/emails/marketing_email.txt +++ b/company_site/public/templates/emails/marketing_email.txt @@ -1,3 +1,8 @@ -{{ subject }} +{{ title }} -{{ content }} \ No newline at end of file +{{ content }} + +— +{{ brand_legal|default:"AI ML Operations, LLC" }} +{{ site_url|default:"https://aimloperations.com" }} +Forward-deployed AI engineering diff --git a/company_site/public/templates/public/preview_email.html b/company_site/public/templates/public/preview_email.html index 0db0636..15d9b3d 100644 --- a/company_site/public/templates/public/preview_email.html +++ b/company_site/public/templates/public/preview_email.html @@ -1,61 +1 @@ - - - - - - Email Template - - - - - - - - \ No newline at end of file +{% extends "emails/marketing_email.html" %} diff --git a/company_site/public/views.py b/company_site/public/views.py index 7f5df63..931e14a 100755 --- a/company_site/public/views.py +++ b/company_site/public/views.py @@ -14,20 +14,25 @@ from django.urls import reverse from django.utils import timezone from django.views.decorators.http import require_POST +from .email_branding import email_brand_context from .forms import FormWithCaptcha from .middleware import get_session_utm from .models import Contact, EmailMessage, PageVisit from .traffic import TRAFFIC_TYPE_LABELS, TrafficType def send_contact_email(email, subject, message): - subject = "New Contact Request for AI ML Operations, LLC" - from_email = "ryan@aimloperations.com" - to="ryan@aimloperations.com" - d = {"subject": subject, "message": message, "email": email} - html_content = get_template(r'emails/contact_email.html').render(d) - text_content = get_template(r'emails/contact_email.txt').render(d) - - msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) + mail_subject = "New Contact Request for AI ML Operations, LLC" + from_email = getattr( + settings, + "DEFAULT_FROM_EMAIL", + "AI ML Operations, LLC ", + ) + 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(mail_subject, text_content, from_email, [to]) msg.attach_alternative(html_content, "text/html") msg.send(fail_silently=True) @@ -81,13 +86,11 @@ def terms_of_service(request): @login_required def preview_email(request, pk): email_instance = get_object_or_404(EmailMessage, pk=pk) - context = { - "title":email_instance.subject, - "content":email_instance.body - } - return render( - request, 'public/preview_email.html', context + context = email_brand_context( + title=email_instance.subject, + content=email_instance.body, ) + return render(request, "emails/marketing_email.html", context) def contact(request): errors = '' diff --git a/docs/email-previews/README.md b/docs/email-previews/README.md new file mode 100644 index 0000000..42e68a9 --- /dev/null +++ b/docs/email-previews/README.md @@ -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`. diff --git a/docs/email-previews/contact.png b/docs/email-previews/contact.png new file mode 100644 index 0000000..5279b55 Binary files /dev/null and b/docs/email-previews/contact.png differ diff --git a/docs/email-previews/invoice.png b/docs/email-previews/invoice.png new file mode 100644 index 0000000..8927479 Binary files /dev/null and b/docs/email-previews/invoice.png differ diff --git a/docs/email-previews/marketing.png b/docs/email-previews/marketing.png new file mode 100644 index 0000000..c692727 Binary files /dev/null and b/docs/email-previews/marketing.png differ