Skip to content

Conversation

@b-saikrishnakanth
Copy link
Collaborator

@b-saikrishnakanth b-saikrishnakanth commented Jan 13, 2026

Description

Generate clean plain text from HTML email template.
Removes all HTML tags, CSS styles, and excessive whitespace.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Screenshots and Media (if applicable)

Test Scenarios

References


Note

Improves plain-text generation for all outbound emails.

  • Add plane/utils/email.py with generate_plain_text_from_html to strip <style> blocks, remove HTML, collapse whitespace, and normalize spacing
  • Replace strip_tags with generate_plain_text_from_html in multiple tasks: email_notification_task, forgot_password_task, magic_link_code_task, project_add_user_email_task, project_invitation_task, workspace_invitation_task, user_activation_email_task, user_deactivation_email_task, user_email_update_task, webhook_task, and analytics export (analytic_plot_export)

Written by Cursor Bugbot for commit 1c7a98d. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

Release Notes

  • Chores
    • Enhanced email text formatting across all notification, invitation, and confirmation emails by implementing a dedicated HTML-to-plain-text converter for improved rendering in text-only email clients.

✏️ Tip: You can customize this high-level summary in your review settings.

@makeplane
Copy link

makeplane bot commented Jan 13, 2026

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

A new utility function generate_plain_text_from_html is introduced in the email utilities module to convert HTML to plaintext. This function is then adopted across ten background task modules, replacing Django's strip_tags for email content generation.

Changes

Cohort / File(s) Summary
New HTML-to-plaintext utility
apps/api/plane/utils/email.py
Added generate_plain_text_from_html() function that removes style blocks via regex, strips HTML tags, condenses blank lines, and returns formatted plaintext.
Background task modules adopting new utility
apps/api/plane/bgtasks/analytic_plot_export.py, email_notification_task.py, forgot_password_task.py, magic_link_code_task.py, project_add_user_email_task.py, project_invitation_task.py, user_activation_email_task.py, user_deactivation_email_task.py, user_email_update_task.py, webhook_task.py, workspace_invitation_task.py
Imported and replaced strip_tags(html_content) with generate_plain_text_from_html(html_content) for text_content generation in email composition.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A plaintext converter hops in with grace,
Replacing old tags throughout the place—
Eleven tasks now share the way,
HTML becomes clean text each day! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[WEB-5917] fix: generate clean plain text from HTML email template' accurately summarizes the main change: introducing a utility to generate clean plain text from HTML emails, replacing strip_tags across multiple email tasks.
Description check ✅ Passed The description includes a clear summary of changes, correctly marks the change type as a bug fix, and provides detailed information via the note section. However, the Test Scenarios and References sections are empty.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/api/plane/utils/email.py (1)

19-42: Consider handling <script> tags and HTML entities for robustness.

The implementation handles <style> tags well, but a few edge cases could improve robustness:

  1. <script> tags: While unlikely in email templates, any script content would leak into plain text.
  2. HTML entities: strip_tags doesn't decode entities like &nbsp;, &amp;, &lt;, etc., which may appear in the plain-text output.
♻️ Suggested enhancement
 # Python imports
+import html
 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
     """
+    if not html_content:
+        return ""
+
     # Remove style tags and their content
     html_content = re.sub(r"<style[^>]*>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
 
+    # Remove script tags and their content (defensive)
+    html_content = re.sub(r"<script[^>]*>.*?</script>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
+
     # Strip HTML tags
     text_content = strip_tags(html_content)
 
+    # Decode HTML entities
+    text_content = html.unescape(text_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
+    # Ensure there's a leading and trailing newline
     text_content = "\n\n" + text_content.lstrip().rstrip() + "\n\n"
 
     return text_content
apps/api/plane/bgtasks/forgot_password_task.py (1)

7-8: Duplicate comment block.

There's a redundant # Third party imports comment on line 8. The Django imports section already has its header on line 7, and third-party imports (Celery) are correctly commented on line 4.

Suggested fix
 # Django imports
-# Third party imports
 from django.core.mail import EmailMultiAlternatives, get_connection
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8399f64 and 1c7a98d.

📒 Files selected for processing (12)
  • apps/api/plane/bgtasks/analytic_plot_export.py
  • apps/api/plane/bgtasks/email_notification_task.py
  • apps/api/plane/bgtasks/forgot_password_task.py
  • apps/api/plane/bgtasks/magic_link_code_task.py
  • apps/api/plane/bgtasks/project_add_user_email_task.py
  • apps/api/plane/bgtasks/project_invitation_task.py
  • apps/api/plane/bgtasks/user_activation_email_task.py
  • apps/api/plane/bgtasks/user_deactivation_email_task.py
  • apps/api/plane/bgtasks/user_email_update_task.py
  • apps/api/plane/bgtasks/webhook_task.py
  • apps/api/plane/bgtasks/workspace_invitation_task.py
  • apps/api/plane/utils/email.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Applied to files:

  • apps/api/plane/utils/email.py
  • apps/api/plane/bgtasks/webhook_task.py
  • apps/api/plane/bgtasks/magic_link_code_task.py
  • apps/api/plane/bgtasks/user_deactivation_email_task.py
  • apps/api/plane/bgtasks/workspace_invitation_task.py
  • apps/api/plane/bgtasks/analytic_plot_export.py
  • apps/api/plane/bgtasks/forgot_password_task.py
  • apps/api/plane/bgtasks/project_add_user_email_task.py
  • apps/api/plane/bgtasks/email_notification_task.py
  • apps/api/plane/bgtasks/user_email_update_task.py
  • apps/api/plane/bgtasks/project_invitation_task.py
  • apps/api/plane/bgtasks/user_activation_email_task.py
🧬 Code graph analysis (11)
apps/api/plane/bgtasks/webhook_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/magic_link_code_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/user_deactivation_email_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/workspace_invitation_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/analytic_plot_export.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/forgot_password_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/project_add_user_email_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/email_notification_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/user_email_update_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/project_invitation_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
apps/api/plane/bgtasks/user_activation_email_task.py (1)
apps/api/plane/utils/email.py (1)
  • generate_plain_text_from_html (19-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (13)
apps/api/plane/bgtasks/workspace_invitation_task.py (1)

14-14: LGTM!

The import and usage of generate_plain_text_from_html are correct. This improves the plain-text email content by removing style blocks and excessive whitespace, which will also benefit the stored message field.

Also applies to: 56-59

apps/api/plane/bgtasks/magic_link_code_task.py (1)

14-14: LGTM!

The import and usage of generate_plain_text_from_html are correct and consistent with the other task files.

Also applies to: 36-36

apps/api/plane/bgtasks/user_deactivation_email_task.py (1)

14-14: LGTM!

The import and usage are correct and follow the same pattern as the other email task files.

Also applies to: 30-30

apps/api/plane/bgtasks/project_add_user_email_task.py (1)

14-14: LGTM!

The import and usage of generate_plain_text_from_html are correct and consistent with the refactor across all email task files.

Also applies to: 58-58

apps/api/plane/bgtasks/user_email_update_task.py (1)

13-13: LGTM!

The import and usage of generate_plain_text_from_html is correctly implemented in both send_email_update_magic_code and send_email_update_confirmation functions. This provides cleaner plain-text email content by properly stripping styles and excessive whitespace.

Also applies to: 35-35, 86-86

apps/api/plane/bgtasks/forgot_password_task.py (1)

14-14: LGTM!

The import and usage of generate_plain_text_from_html is correctly implemented in the forgot_password function.

Also applies to: 44-44

apps/api/plane/bgtasks/analytic_plot_export.py (1)

20-20: LGTM!

The import and usage of generate_plain_text_from_html is correctly implemented in the send_export_email helper function, which serves both analytic_export_task and export_analytics_to_csv_email tasks.

Also applies to: 51-51

apps/api/plane/bgtasks/email_notification_task.py (1)

19-19: LGTM!

The import and usage of generate_plain_text_from_html is correctly implemented in the send_email_notification task. The plain-text conversion now properly handles style tags and excessive whitespace.

Also applies to: 259-259

apps/api/plane/bgtasks/user_activation_email_task.py (1)

14-14: LGTM!

The import and usage of generate_plain_text_from_html is correctly implemented in the user_activation_email task.

Also applies to: 30-30

apps/api/plane/bgtasks/webhook_task.py (2)

49-49: LGTM!

Import is correctly placed with other module imports and aligns with the PR objective.


220-221: LGTM!

The replacement of strip_tags with generate_plain_text_from_html properly handles style tag removal and whitespace normalization for cleaner email plain-text output.

apps/api/plane/bgtasks/project_invitation_task.py (2)

15-15: LGTM!

Import is correctly added alongside other module imports.


38-43: LGTM!

The change correctly applies the improved HTML-to-text conversion. Note that generate_plain_text_from_html adds leading/trailing newlines to the output, which will now be stored in project_member_invite.message. This should be fine as it's whitespace normalization for consistent formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants