diff --git a/frappe/__init__.py b/frappe/__init__.py index 93352d1e62..25cb85952c 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -516,6 +516,7 @@ def sendmail( with_container=False, email_read_tracker_url=None, x_priority: Literal[1, 3, 5] = 3, + email_headers=None, ) -> Optional["EmailQueue"]: """Send email using user's default **Email Account** or global default **Email Account**. @@ -544,6 +545,7 @@ def sendmail( :param header: Append header in email :param with_container: Wraps email inside a styled container :param x_priority: 1 = HIGHEST, 3 = NORMAL, 5 = LOWEST + :param email_headers: Additional headers to be added in the email, e.g. {"X-Custom-Header": "value"} or {"Custom-Header": "value"}. Automatically prepends "X-" to the header name if not present. """ if recipients is None: @@ -600,6 +602,7 @@ def sendmail( with_container=with_container, email_read_tracker_url=email_read_tracker_url, x_priority=x_priority, + email_headers=email_headers, ) # 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 1b42df7b2e..526188f050 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -514,6 +514,7 @@ class QueueBuilder: with_container=False, email_read_tracker_url=None, x_priority: Literal[1, 3, 5] = 3, + email_headers=None, ): """Add email to sending queue (Email Queue) @@ -540,6 +541,7 @@ class QueueBuilder: :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 + :param email_headers: Additional headers to be added in the email, e.g. {"X-Custom-Header": "value"} or {"Custom-Header": "value"}. Automatically prepends "X-" to the header name if not present. """ self._unsubscribe_method = unsubscribe_method @@ -576,6 +578,7 @@ class QueueBuilder: self.inline_images = inline_images self.print_letterhead = print_letterhead self.email_read_tracker_url = email_read_tracker_url + self.email_headers = email_headers @property def unsubscribe_method(self): @@ -728,6 +731,10 @@ class QueueBuilder: ) mail.set_message_id(self.message_id, self.is_notification) + + if self.email_headers: + mail.add_headers(self.email_headers) + if self.read_receipt: mail.msg_root["Disposition-Notification-To"] = self.sender if self.in_reply_to: diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index afc9890b3d..7a2256fdda 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -12,6 +12,7 @@ from email.mime.multipart import MIMEMultipart from typing import TYPE_CHECKING import frappe +from frappe import _ from frappe.email.doctype.email_account.email_account import EmailAccount from frappe.utils import ( cint, @@ -315,6 +316,16 @@ class EMail: """Used to send the Message-Id of a received email back as In-Reply-To""" self.set_header("In-Reply-To", in_reply_to) + def add_headers(self, headers): + """Add custom headers to the email""" + if not isinstance(headers, dict): + frappe.throw(_("Headers must be a dictionary")) + + for key, value in headers.items(): + if value is not None: + key = "X-" + key if not key.startswith("X-") else key + self.set_header(key, value) + def make(self): """build into msg_root""" headers = {