[WEB-5917] fix: generate clean plain text from HTML email template #8535

This commit is contained in:
b-saikrishnakanth
2026-02-17 00:44:52 +05:30
committed by GitHub
parent e9b011896d
commit f0dcf66167
12 changed files with 65 additions and 23 deletions

View File

@@ -13,7 +13,6 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.db.models import Q, Case, Value, When
from django.db import models
from django.db.models.functions import Concat
@@ -22,6 +21,7 @@ from django.db.models.functions import Concat
from plane.db.models import Issue
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.analytics_plot import build_graph_plot
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.utils.issue_filters import issue_filters
from plane.utils.csv_utils import sanitize_csv_row
@@ -53,7 +53,7 @@ def send_export_email(email, slug, csv_buffer, rows):
"""Helper function to send export email."""
subject = "Your Export is ready"
html_content = render_to_string("emails/exports/analytics.html", {})
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
csv_buffer.seek(0)

View File

@@ -15,12 +15,12 @@ from django.template.loader import render_to_string
# Django imports
from django.utils import timezone
from django.utils.html import strip_tags
# Module imports
from plane.db.models import EmailNotificationLog, Issue, User
from plane.license.utils.instance_value import get_email_configuration
from plane.settings.redis import redis_instance
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -260,7 +260,7 @@ def send_email_notification(issue_id, notification_data, receiver_id, email_noti
"entity_type": "issue",
}
html_content = render_to_string("emails/notifications/issue-updates.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
try:
connection = get_connection(

View File

@@ -12,10 +12,10 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -45,7 +45,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
html_content = render_to_string("emails/auth/forgot_password.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,

View File

@@ -12,10 +12,10 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -37,7 +37,7 @@ def magic_link(email, key, token):
context = {"code": token, "email": email}
html_content = render_to_string("emails/auth/magic_signin.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,

View File

@@ -11,11 +11,11 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.db.models import ProjectMember
from plane.db.models import User
@@ -59,7 +59,7 @@ def project_add_user_email(current_site, project_member_id, invitor_id):
# Render the email template
html_content = render_to_string("emails/notifications/project_addition.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
# Initialize the connection
connection = get_connection(
host=EMAIL_HOST,

View File

@@ -12,11 +12,11 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.db.models import Project, ProjectMemberInvite, User
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -41,7 +41,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
html_content = render_to_string("emails/invitations/project_invitation.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
project_member_invite.message = text_content
project_member_invite.save()

View File

@@ -8,7 +8,6 @@ import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
@@ -16,6 +15,7 @@ from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -31,7 +31,7 @@ def user_activation_email(current_site, user_id):
# Send email to user
html_content = render_to_string("emails/user/user_activation.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
# Configure email connection from the database
(
EMAIL_HOST,

View File

@@ -8,7 +8,6 @@ import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
@@ -16,6 +15,7 @@ from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -31,7 +31,7 @@ def user_deactivation_email(current_site, user_id):
# Send email to user
html_content = render_to_string("emails/user/user_deactivation.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
# Configure email connection from the database
(
EMAIL_HOST,

View File

@@ -11,10 +11,10 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -36,7 +36,7 @@ def send_email_update_magic_code(email, token):
context = {"code": token, "email": email}
html_content = render_to_string("emails/auth/magic_signin.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,
@@ -87,7 +87,7 @@ def send_email_update_confirmation(email):
context = {"email": email}
html_content = render_to_string("emails/user/email_updated.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,

View File

@@ -20,7 +20,6 @@ from django.db.models import Prefetch
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.serializers.json import DjangoJSONEncoder
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.core.exceptions import ObjectDoesNotExist
# Module imports
@@ -51,6 +50,7 @@ from plane.db.models import (
IssueAssignee,
)
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.settings.mongo import MongoConnection
@@ -222,7 +222,7 @@ def send_webhook_deactivation_email(webhook_id: str, receiver_id: str, current_s
"webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
}
html_content = render_to_string("emails/notifications/webhook-deactivate.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
# Set the email connection
connection = get_connection(

View File

@@ -11,11 +11,11 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
from django.utils.html import strip_tags
# Module imports
from plane.db.models import User, Workspace, WorkspaceMemberInvite
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -57,7 +57,7 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter):
html_content = render_to_string("emails/invitations/workspace_invitation.html", context)
text_content = strip_tags(html_content)
text_content = generate_plain_text_from_html(html_content)
workspace_member_invite.message = text_content
workspace_member_invite.save()

View File

@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: 2023-present Plane Software, Inc.
# SPDX-License-Identifier: LicenseRef-Plane-Commercial
#
# Licensed under the Plane Commercial License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# https://plane.so/legals/eula
#
# DO NOT remove or modify this notice.
# NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited.
# Python imports
import re
# Django imports
from django.utils.html import strip_tags
def generate_plain_text_from_html(html_content):
"""
Generate clean plain text from HTML email template.
Removes all HTML tags, CSS styles, and excessive whitespace.
Args:
html_content (str): The HTML content to convert to plain text
Returns:
str: Clean plain text without HTML tags, styles, or excessive whitespace
"""
# Remove style tags and their content
html_content = re.sub(r"<style[^>]*>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
# Strip HTML tags
text_content = strip_tags(html_content)
# Remove excessive empty lines
text_content = re.sub(r"\n\s*\n\s*\n+", "\n\n", text_content)
# Ensure there's a leading and trailing whitespace
text_content = "\n\n" + text_content.lstrip().rstrip() + "\n\n"
return text_content