From ddd99619e4ee04b3baf3b095d53eff808e415e74 Mon Sep 17 00:00:00 2001 From: syntaxlord Date: Tue, 23 Jun 2026 12:10:33 +0530 Subject: [PATCH 1/2] Add organization branding to unsubscribe page --- backend/campaigns/views.py | 38 +++++++++++++++---- ...03_organization_brand_logo_url_and_more.py | 28 ++++++++++++++ backend/tenants/models.py | 15 ++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 backend/tenants/migrations/0003_organization_brand_logo_url_and_more.py diff --git a/backend/campaigns/views.py b/backend/campaigns/views.py index 2a11bf0..031652d 100644 --- a/backend/campaigns/views.py +++ b/backend/campaigns/views.py @@ -718,7 +718,12 @@ def _build_fallback_content(self, request): from .utils import verify_unsubscribe_token -def _unsubscribe_page(title, message, extra_html=''): +def _unsubscribe_page(title, message, extra_html='', logo_url=''): + logo_html = ( + f'Organization Logo' + if logo_url else '' + ) + return ( '' '' @@ -732,7 +737,7 @@ def _unsubscribe_page(title, message, extra_html=''): 'button{margin-top:12px;border:0;border-radius:999px;background:#1d4ed8;color:#fff;font-weight:700;padding:12px 20px;cursor:pointer;}' '' '' - f'

{title}

{message}

{extra_html}
' + f'
{logo_html}

{title}

{message}

{extra_html}
' '' ) @@ -749,6 +754,20 @@ def unsubscribe_view(request, lead_id, token): try: lead = Lead.objects.get(id=lead_id) + + organization = lead.organization + + custom_title = ( + organization.unsubscribe_title + or "Confirm unsubscribe" + ) + + custom_message = ( + organization.unsubscribe_message + or "Please confirm that you want to unsubscribe from future emails sent through LeadOrbit." + ) + + logo_url = organization.brand_logo_url or "" except Lead.DoesNotExist: return HttpResponse( "Lead not found", @@ -764,20 +783,23 @@ def unsubscribe_view(request, lead_id, token): '' ) html = _unsubscribe_page( - 'Confirm unsubscribe', - 'Please confirm that you want to unsubscribe from future emails sent through LeadOrbit.', + custom_title, + custom_message, form, + logo_url ) + return HttpResponse(html, content_type='text/html') lead.global_unsubscribe = True lead.save(update_fields=["global_unsubscribe"]) html = _unsubscribe_page( - 'Unsubscribed', - 'You have been unsubscribed from all future emails sent through LeadOrbit.', - '

If you received this link by mistake, no further action is needed.

', - ) + 'Unsubscribed', + 'You have been unsubscribed from all future emails sent through LeadOrbit.', + '

If you received this link by mistake, no further action is needed.

', + logo_url, +) return HttpResponse(html, content_type='text/html') diff --git a/backend/tenants/migrations/0003_organization_brand_logo_url_and_more.py b/backend/tenants/migrations/0003_organization_brand_logo_url_and_more.py new file mode 100644 index 0000000..d5d87ca --- /dev/null +++ b/backend/tenants/migrations/0003_organization_brand_logo_url_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.14 on 2026-06-22 05:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0002_organization_enable_ai_personalization_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='brand_logo_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='organization', + name='unsubscribe_message', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='organization', + name='unsubscribe_title', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/backend/tenants/models.py b/backend/tenants/models.py index bb58b37..e13fef4 100644 --- a/backend/tenants/models.py +++ b/backend/tenants/models.py @@ -9,7 +9,22 @@ class Organization(models.Model): created_at = models.DateTimeField(auto_now_add=True) gemini_api_key = models.CharField(max_length=255, blank=True, null=True) enable_ai_personalization = models.BooleanField(default=True) + + unsubscribe_title = models.CharField( + max_length=255, + blank=True, + null=True + ) + unsubscribe_message = models.TextField( + blank=True, + null=True + ) + + brand_logo_url = models.URLField( + blank=True, + null=True + ) def __str__(self): return self.name From e98ea3272195ea61c4306c531532ada9d3d4559b Mon Sep 17 00:00:00 2001 From: syntaxlord Date: Tue, 23 Jun 2026 12:32:19 +0530 Subject: [PATCH 2/2] Escape organization branding fields in unsubscribe page --- backend/campaigns/views.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/campaigns/views.py b/backend/campaigns/views.py index 031652d..7eadc69 100644 --- a/backend/campaigns/views.py +++ b/backend/campaigns/views.py @@ -714,14 +714,19 @@ def _build_fallback_content(self, request): from django.http import HttpResponse from django.middleware.csrf import get_token +from django.utils.html import escape from leads.models import Lead from .utils import verify_unsubscribe_token def _unsubscribe_page(title, message, extra_html='', logo_url=''): + safe_title = escape(title or "") + safe_message = escape(message or "") + safe_logo_url = escape(logo_url or "") + logo_html = ( - f'Organization Logo' - if logo_url else '' + f'Organization Logo' + if safe_logo_url else '' ) return ( @@ -730,14 +735,14 @@ def _unsubscribe_page(title, message, extra_html='', logo_url=''): '' '' '' - f'{title} | LeadOrbit' + f'{safe_title} | LeadOrbit' '' '' - f'
{logo_html}

{title}

{message}

{extra_html}
' + f'
{logo_html}

{safe_title}

{safe_message}

{extra_html}
' '' )