From 89ed7c90a7dd680a3c7082c4b0182c06795a6555 Mon Sep 17 00:00:00 2001 From: jll-02 <63648645+jll-02@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:48:19 +0200 Subject: [PATCH] feat: :sparkles: addes x-priority option to email header and the frappe.sendmail function (#31966) creating the option to give the email an importance. 1 = Highest, 3 = Normal, 5 = Lowest commonly used to flag the importance of emails Co-authored-by: Jan Lukas Liesen <=> --- frappe/__init__.py | 3 +++ .../email/doctype/email_queue/email_queue.py | 14 ++++++++++++-- frappe/email/email_body.py | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 9c6d4a81bb..11601580ac 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -507,6 +507,7 @@ def sendmail( print_letterhead=False, with_container=False, email_read_tracker_url=None, + x_priority: Literal[1, 3, 5] = 3, ) -> Optional["EmailQueue"]: """Send email using user's default **Email Account** or global default **Email Account**. @@ -534,6 +535,7 @@ def sendmail( :param args: Arguments for rendering the template :param header: Append header in email :param with_container: Wraps email inside a styled container + :param x_priority: 1 = HIGHEST, 3 = NORMAL, 5 = LOWEST """ if recipients is None: @@ -589,6 +591,7 @@ def sendmail( print_letterhead=print_letterhead, with_container=with_container, email_read_tracker_url=email_read_tracker_url, + x_priority=x_priority, ) # build email queue and send the email if send_now is True. diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 3aea002fed..71a090122a 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -1,12 +1,15 @@ # Copyright (c) 2015, Frappe Technologies and contributors # License: MIT. See LICENSE +from __future__ import annotations + import json import quopri import traceback from contextlib import suppress from email.parser import Parser from email.policy import SMTP +from typing import TYPE_CHECKING import frappe from frappe import _, safe_encode, task @@ -34,6 +37,9 @@ from frappe.utils import ( ) from frappe.utils.verified_command import get_signed_params +if TYPE_CHECKING: + from typing import Literal + class EmailQueue(Document): # begin: auto-generated types @@ -88,7 +94,7 @@ class EmailQueue(Document): return duplicate @classmethod - def new(cls, doc_data, ignore_permissions=False) -> "EmailQueue": + def new(cls, doc_data, ignore_permissions=False) -> EmailQueue: data = doc_data.copy() if not data.get("recipients"): return @@ -104,7 +110,7 @@ class EmailQueue(Document): return doc @classmethod - def find(cls, name) -> "EmailQueue": + def find(cls, name) -> EmailQueue: return frappe.get_doc(cls.DOCTYPE, name) @classmethod @@ -502,6 +508,7 @@ class QueueBuilder: print_letterhead=False, with_container=False, email_read_tracker_url=None, + x_priority: Literal[1, 3, 5] = 3, ): """Add email to sending queue (Email Queue) @@ -527,6 +534,7 @@ class QueueBuilder: :param header: Append header in email (boolean) :param with_container: Wraps email inside styled container :param email_read_tracker_url: A URL for tracking whether an email is read by the recipient. + :param x_priority: 1 = HIGHEST, 3 = NORMAL, 5 = LOWEST """ self._unsubscribe_method = unsubscribe_method @@ -537,6 +545,7 @@ class QueueBuilder: self._sender = sender self._text_content = text_content self._message = message + self._x_priority: Literal[1, 3, 5] = x_priority self._add_unsubscribe_link = add_unsubscribe_link self._unsubscribe_message = unsubscribe_message self._attachments = attachments @@ -710,6 +719,7 @@ class QueueBuilder: expose_recipients=self.expose_recipients, inline_images=self.inline_images, header=self.header, + x_priority=self._x_priority, ) mail.set_message_id(self.message_id, self.is_notification) diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 34308f1af8..352ef87de5 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -1,11 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE + +from __future__ import annotations + import email.utils import os import re from email import policy from email.header import Header from email.mime.multipart import MIMEMultipart +from typing import TYPE_CHECKING import frappe from frappe.email.doctype.email_account.email_account import EmailAccount @@ -23,6 +27,9 @@ from frappe.utils import ( ) from frappe.utils.pdf import get_pdf +if TYPE_CHECKING: + from typing import Literal + EMBED_PATTERN = re.compile("""embed=["'](.*?)["']""") @@ -44,6 +51,7 @@ def get_email( expose_recipients=None, inline_images=None, header=None, + x_priority: Literal[1, 3, 5] = 3, ): """Prepare an email with the following format: - multipart/mixed @@ -72,6 +80,7 @@ def get_email( bcc=bcc, email_account=email_account, expose_recipients=expose_recipients, + x_priority=x_priority, ) if not content.strip().startswith("<"): @@ -117,6 +126,7 @@ class EMail: bcc=(), email_account=None, expose_recipients=None, + x_priority: Literal[1, 3, 5] = 3, ): from email import charset as Charset @@ -142,6 +152,8 @@ class EMail: self.bcc = bcc or [] self.html_set = False + self.x_priority: Literal[1, 3, 5] = x_priority + self.email_account = email_account or EmailAccount.find_outgoing( match_by_email=sender, _raise_error=True ) @@ -315,6 +327,13 @@ class EMail: "X-Frappe-Site": get_url(), } + if self.x_priority != 3: + headers.update( + { + "X-Priority": str(self.x_priority), + } + ) + # reset headers as values may be changed. for key, val in headers.items(): if val: