diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py
index e6e13dafe7..c585a81826 100644
--- a/frappe/desk/doctype/notification_log/notification_log.py
+++ b/frappe/desk/doctype/notification_log/notification_log.py
@@ -29,6 +29,7 @@ class NotificationLog(Document):
read: DF.Check
subject: DF.Text | None
type: DF.Literal["Mention", "Energy Point", "Assignment", "Share", "Alert"]
+
# end: auto-generated types
def after_insert(self):
frappe.publish_realtime("notification", after_commit=True, user=self.for_user)
@@ -115,18 +116,17 @@ def _get_user_ids(user_emails):
return [user for user in user_names if is_notifications_enabled(user)]
-def send_notification_email(doc):
-
+def send_notification_email(doc: NotificationLog):
if doc.type == "Energy Point" and doc.email_content is None:
return
from frappe.utils import get_url_to_form, strip_html
- email = frappe.db.get_value("User", doc.for_user, "email")
- if not email:
+ user = frappe.db.get_value("User", doc.for_user, fieldname=["email", "language"], as_dict=True)
+ if not user:
return
- header = get_email_header(doc)
+ header = get_email_header(doc, user.language)
email_subject = strip_html(doc.subject)
args = {
"body_content": doc.subject,
@@ -140,7 +140,7 @@ def send_notification_email(doc):
args["doc_link"] = get_url_to_form(doc.document_type, doc.document_name)
frappe.sendmail(
- recipients=email,
+ recipients=user.email,
subject=email_subject,
template="new_notification",
args=args,
@@ -149,14 +149,14 @@ def send_notification_email(doc):
)
-def get_email_header(doc):
+def get_email_header(doc, language: str | None = None):
docname = doc.document_name
header_map = {
- "Default": _("New Notification"),
- "Mention": _("New Mention on {0}").format(docname),
- "Assignment": _("Assignment Update on {0}").format(docname),
- "Share": _("New Document Shared {0}").format(docname),
- "Energy Point": _("Energy Point Update on {0}").format(docname),
+ "Default": _("New Notification", lang=language),
+ "Mention": _("New Mention on {0}", lang=language).format(docname),
+ "Assignment": _("Assignment Update on {0}", lang=language).format(docname),
+ "Share": _("New Document Shared {0}", lang=language).format(docname),
+ "Energy Point": _("Energy Point Update on {0}", lang=language).format(docname),
}
return header_map[doc.type or "Default"]
diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py
index dc8dbc7cf5..e0de030303 100644
--- a/frappe/desk/form/assign_to.py
+++ b/frappe/desk/form/assign_to.py
@@ -253,8 +253,10 @@ def notify_assignment(
if not (assigned_by and allocated_to and doc_type and doc_name):
return
+ assigned_user = frappe.db.get_value("User", allocated_to, ["language", "enabled"], as_dict=True)
+
# return if self assigned or user disabled
- if assigned_by == allocated_to or not frappe.db.get_value("User", allocated_to, "enabled"):
+ if assigned_by == allocated_to or not assigned_user.enabled:
return
# Search for email address in description -- i.e. assignee
@@ -263,14 +265,16 @@ def notify_assignment(
description_html = f"
{description}
" if description else None
if action == "CLOSE":
- subject = _("Your assignment on {0} {1} has been removed by {2}").format(
- frappe.bold(_(doc_type)), get_title_html(title), frappe.bold(user_name)
- )
+ subject = _(
+ "Your assignment on {0} {1} has been removed by {2}", lang=assigned_user.language
+ ).format(frappe.bold(_(doc_type)), get_title_html(title), frappe.bold(user_name))
else:
user_name = frappe.bold(user_name)
- document_type = frappe.bold(_(doc_type))
+ document_type = frappe.bold(_(doc_type, lang=assigned_user.language))
title = get_title_html(title)
- subject = _("{0} assigned a new task {1} {2} to you").format(user_name, document_type, title)
+ subject = _("{0} assigned a new task {1} {2} to you", lang=assigned_user.language).format(
+ user_name, document_type, title
+ )
notification_doc = {
"type": "Assignment",
diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py
index 828ae2e419..91ca2bbdbb 100644
--- a/frappe/email/doctype/email_queue/email_queue.py
+++ b/frappe/email/doctype/email_queue/email_queue.py
@@ -6,7 +6,7 @@ import quopri
import traceback
from contextlib import suppress
from email.parser import Parser
-from email.policy import SMTPUTF8, default
+from email.policy import SMTP
import frappe
from frappe import _, safe_encode, task
@@ -169,7 +169,9 @@ class EmailQueue(Document):
else:
if not frappe.flags.in_test or frappe.flags.testing_email:
ctx.smtp_server.session.sendmail(
- from_addr=self.sender, to_addrs=recipient.recipient, msg=message
+ from_addr=self.sender,
+ to_addrs=recipient.recipient,
+ msg=message.decode("utf-8").encode(),
)
ctx.update_recipient_status_to_sent(recipient)
@@ -264,7 +266,7 @@ class SendMailContext:
@savepoint(catch=Exception)
def notify_failed_email(self):
# Parse the email body to extract the subject
- subject = Parser(policy=default).parsestr(self.queue_doc.message)["Subject"]
+ subject = Parser(policy=SMTP).parsestr(self.queue_doc.message)["Subject"]
# Construct the notification
notification = frappe.new_doc("Notification Log")
@@ -281,7 +283,7 @@ class SendMailContext:
recipient.update_db(status="Sent", commit=True)
def get_message_object(self, message):
- return Parser(policy=SMTPUTF8).parsestr(message)
+ return Parser(policy=SMTP).parsestr(message)
def message_placeholder(self, placeholder_key):
# sourcery skip: avoid-builtin-shadow
@@ -293,9 +295,10 @@ class SendMailContext:
}
return map.get(placeholder_key)
- def build_message(self, recipient_email):
+ def build_message(self, recipient_email) -> bytes:
"""Build message specific to the recipient."""
message = self.queue_doc.message
+
if not message:
return ""
diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py
index 449c0b5b15..80348df394 100755
--- a/frappe/email/email_body.py
+++ b/frappe/email/email_body.py
@@ -1,6 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
-
import email.utils
import os
import re
@@ -136,8 +135,8 @@ class EMail:
self.subject = subject
self.expose_recipients = expose_recipients
- self.msg_root = MIMEMultipart("mixed", policy=policy.SMTPUTF8)
- self.msg_alternative = MIMEMultipart("alternative", policy=policy.SMTPUTF8)
+ self.msg_root = MIMEMultipart("mixed", policy=policy.SMTP)
+ self.msg_alternative = MIMEMultipart("alternative", policy=policy.SMTP)
self.msg_root.attach(self.msg_alternative)
self.cc = cc or []
self.bcc = bcc or []
@@ -186,7 +185,7 @@ class EMail:
"""
from email.mime.text import MIMEText
- part = MIMEText(message, "plain", "utf-8", policy=policy.SMTPUTF8)
+ part = MIMEText(message, "plain", "utf-8", policy=policy.SMTP)
self.msg_alternative.attach(part)
def set_part_html(self, message, inline_images):
@@ -199,9 +198,9 @@ class EMail:
message, _inline_images = replace_filename_with_cid(message)
# prepare parts
- msg_related = MIMEMultipart("related", policy=policy.SMTPUTF8)
+ msg_related = MIMEMultipart("related", policy=policy.SMTP)
- html_part = MIMEText(message, "html", "utf-8", policy=policy.SMTPUTF8)
+ html_part = MIMEText(message, "html", "utf-8", policy=policy.SMTP)
msg_related.attach(html_part)
for image in _inline_images:
@@ -215,7 +214,7 @@ class EMail:
self.msg_alternative.attach(msg_related)
else:
- self.msg_alternative.attach(MIMEText(message, "html", "utf-8", policy=policy.SMTPUTF8))
+ self.msg_alternative.attach(MIMEText(message, "html", "utf-8", policy=policy.SMTP))
def set_html_as_text(self, html):
"""Set plain text from HTML"""
@@ -228,7 +227,7 @@ class EMail:
from email.mime.text import MIMEText
maintype, subtype = mime_type.split("/")
- part = MIMEText(message, _subtype=subtype, policy=policy.SMTPUTF8)
+ part = MIMEText(message, _subtype=subtype, policy=policy.SMTP)
if as_attachment:
part.add_header("Content-Disposition", "attachment", filename=filename)
@@ -342,7 +341,7 @@ class EMail:
"""validate, build message and convert to string"""
self.validate()
self.make()
- return self.msg_root.as_string(policy=policy.SMTPUTF8)
+ return self.msg_root.as_string(policy=policy.SMTP)
def get_formatted_html(