feat: add custom headers to any email in frappe.sendmail (#32820)

* feat: add custom headers to any email in frappe.sendmail

* fix: add headers only when it is provided

* fix: prepend headers with 'X='
This commit is contained in:
RitvikSardana 2025-06-10 12:38:02 +05:30 committed by GitHub
parent 271fe0e47e
commit c193aad863
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 21 additions and 0 deletions

View file

@ -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.

View file

@ -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:

View file

@ -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 = {