feat: Send Mails via Frappe Mail
This commit is contained in:
parent
c01eb68b83
commit
527ac29c2e
3 changed files with 130 additions and 19 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:email_account_name",
|
||||
"creation": "2014-09-11 12:04:34.163728",
|
||||
"creation": "2024-06-11 16:39:01.323289",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
|
|
@ -15,6 +15,11 @@
|
|||
"column_break_3",
|
||||
"domain",
|
||||
"service",
|
||||
"frappe_mail_site",
|
||||
"authentication_section",
|
||||
"api_key",
|
||||
"column_break_ghqa",
|
||||
"api_secret",
|
||||
"authentication_column",
|
||||
"auth_method",
|
||||
"authorize_api_access",
|
||||
|
|
@ -50,19 +55,21 @@
|
|||
"notify_if_unreplied",
|
||||
"unreplied_for_mins",
|
||||
"send_notification_to",
|
||||
"outgoing_smtp_tab",
|
||||
"outgoing_mail_settings",
|
||||
"column_break_bidn",
|
||||
"use_tls",
|
||||
"use_ssl_for_outgoing",
|
||||
"smtp_server",
|
||||
"smtp_port",
|
||||
"column_break_38",
|
||||
"outgoing_tab",
|
||||
"section_break_nesl",
|
||||
"column_break_y6hx",
|
||||
"column_break_h5pd",
|
||||
"default_outgoing",
|
||||
"always_use_account_email_id_as_sender",
|
||||
"always_use_account_name_as_sender_name",
|
||||
"send_unsubscribe_message",
|
||||
"track_email_status",
|
||||
"outgoing_mail_settings",
|
||||
"use_tls",
|
||||
"use_ssl_for_outgoing",
|
||||
"smtp_server",
|
||||
"smtp_port",
|
||||
"column_break_38",
|
||||
"no_smtp_authentication",
|
||||
"signature_section",
|
||||
"add_signature",
|
||||
|
|
@ -289,6 +296,7 @@
|
|||
"mandatory_depends_on": "notify_if_unreplied"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service != \"Frappe Mail\"",
|
||||
"fieldname": "outgoing_mail_settings",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
|
|
@ -533,6 +541,7 @@
|
|||
"label": "Brand Logo"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service != \"Frappe Mail\"",
|
||||
"fieldname": "authentication_column",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Authentication"
|
||||
|
|
@ -624,20 +633,58 @@
|
|||
"label": "Incoming (POP/IMAP)"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_outgoing",
|
||||
"fieldname": "outgoing_smtp_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Outgoing (SMTP)"
|
||||
"fieldname": "api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Key",
|
||||
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bidn",
|
||||
"fieldname": "api_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "API Secret",
|
||||
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\""
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service == \"Frappe Mail\"",
|
||||
"fieldname": "authentication_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Authentication"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ghqa",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "https://frappemail.com",
|
||||
"depends_on": "eval: doc.service == \"Frappe Mail\"",
|
||||
"fieldname": "frappe_mail_site",
|
||||
"fieldtype": "Data",
|
||||
"label": "Frappe Mail Site",
|
||||
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\""
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_outgoing",
|
||||
"fieldname": "outgoing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Outgoing"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_nesl",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_y6hx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_h5pd",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-inbox",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-10 18:18:08.403133",
|
||||
"modified": "2024-06-11 16:54:22.255325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ class EmailAccount(Document):
|
|||
add_signature: DF.Check
|
||||
always_use_account_email_id_as_sender: DF.Check
|
||||
always_use_account_name_as_sender_name: DF.Check
|
||||
api_key: DF.Data | None
|
||||
api_secret: DF.Password | None
|
||||
append_emails_to_sent_folder: DF.Check
|
||||
append_to: DF.Link | None
|
||||
ascii_encode_password: DF.Check
|
||||
|
|
@ -84,6 +86,7 @@ class EmailAccount(Document):
|
|||
enable_incoming: DF.Check
|
||||
enable_outgoing: DF.Check
|
||||
footer: DF.TextEditor | None
|
||||
frappe_mail_site: DF.Data | None
|
||||
imap_folder: DF.Table[IMAPFolder]
|
||||
incoming_port: DF.Data | None
|
||||
initial_sync_count: DF.Literal["100", "250", "500"]
|
||||
|
|
@ -152,7 +155,11 @@ class EmailAccount(Document):
|
|||
self.awaiting_password = 0
|
||||
self.password = None
|
||||
|
||||
if not frappe.local.flags.in_install and not self.awaiting_password:
|
||||
if (
|
||||
not frappe.local.flags.in_install
|
||||
and not self.awaiting_password
|
||||
and not self.service == "Frappe Mail"
|
||||
):
|
||||
if validate_oauth or self.password or self.smtp_server in ("127.0.0.1", "localhost"):
|
||||
if self.enable_incoming:
|
||||
self.get_incoming_server()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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
|
||||
|
|
@ -16,6 +17,7 @@ from frappe.email.doctype.email_account.email_account import EmailAccount
|
|||
from frappe.email.email_body import add_attachment, get_email, get_formatted_html
|
||||
from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message
|
||||
from frappe.email.smtp import SMTPServer
|
||||
from frappe.frappeclient import FrappeClient
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType, Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
|
|
@ -34,6 +36,9 @@ from frappe.utils import (
|
|||
from frappe.utils.deprecations import deprecated
|
||||
from frappe.utils.verified_command import get_signed_params
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from requests import Response
|
||||
|
||||
|
||||
class EmailQueue(Document):
|
||||
# begin: auto-generated types
|
||||
|
|
@ -168,8 +173,14 @@ class EmailQueue(Document):
|
|||
message = ctx.build_message(recipient.recipient)
|
||||
if method := get_hook_method("override_email_send"):
|
||||
method(self, self.sender, recipient.recipient, message)
|
||||
else:
|
||||
if not frappe.flags.in_test or frappe.flags.testing_email:
|
||||
elif not frappe.flags.in_test or frappe.flags.testing_email:
|
||||
if ctx.email_account_doc.service == "Frappe Mail":
|
||||
ctx.frappe_mail.send(
|
||||
sender=self.sender,
|
||||
recipients=recipient.recipient,
|
||||
message=message.decode("utf-8"),
|
||||
)
|
||||
else:
|
||||
ctx.smtp_server.session.sendmail(
|
||||
from_addr=self.sender,
|
||||
to_addrs=recipient.recipient,
|
||||
|
|
@ -240,7 +251,10 @@ class SendMailContext:
|
|||
|
||||
def fetch_smtp_server(self):
|
||||
self.email_account_doc = self.queue_doc.get_email_account(raise_error=True)
|
||||
if not self.smtp_server:
|
||||
|
||||
if self.email_account_doc.service == "Frappe Mail":
|
||||
self.frappe_mail = FrappeMail(self.email_account_doc)
|
||||
elif not self.smtp_server:
|
||||
self.smtp_server = self.email_account_doc.get_smtp_server()
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -802,3 +816,46 @@ class QueueBuilder:
|
|||
d["recipients"] = self.final_recipients()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class FrappeMail:
|
||||
def __init__(self, email_account: "EmailAccount") -> None:
|
||||
self.client = self.get_client(email_account)
|
||||
self.frappe_mail_site = email_account.frappe_mail_site
|
||||
|
||||
@staticmethod
|
||||
def get_client(email_account: "EmailAccount") -> FrappeClient:
|
||||
"""Returns FrappeClient object for the given email account."""
|
||||
|
||||
if hasattr(frappe.local, "frappe_mail_clients"):
|
||||
if client := frappe.local.frappe_mail_clients.get(email_account.name):
|
||||
return client
|
||||
else:
|
||||
frappe.local.frappe_mail_clients = {}
|
||||
|
||||
url = email_account.frappe_mail_site
|
||||
api_key = email_account.api_key
|
||||
api_secret = email_account.get_password("api_secret")
|
||||
|
||||
client = FrappeClient(url, api_key=api_key, api_secret=api_secret)
|
||||
frappe.local.frappe_mail_clients[email_account.name] = client
|
||||
|
||||
return client
|
||||
|
||||
def request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
data: dict | None = None,
|
||||
timeout: int | tuple[int, int] = (60, 120),
|
||||
) -> "Response":
|
||||
url = f"{self.frappe_mail_site}/{endpoint}"
|
||||
response = self.client.session.request(
|
||||
method=method, url=url, headers=self.client.headers, json=data, timeout=timeout
|
||||
)
|
||||
return response
|
||||
|
||||
def send(self, sender: str, recipients: str, message: str):
|
||||
endpoint = "outbound/send-raw"
|
||||
data = {"from": sender, "to": recipients, "raw_message": message}
|
||||
self.request("POST", endpoint, data)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue