From 248be4fb369001c1f2b0b306d77c5460bcafdbe4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 15 Nov 2024 16:16:25 +0530 Subject: [PATCH] refactor: Outbound API --- README.md | 68 +-------------------- mail_client/api/outbound.py | 115 +++++++++++------------------------- mail_client/hooks.py | 15 ----- 3 files changed, 36 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index de40eb65..d31e68da 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ These endpoints facilitate sending emails from the Frappe Mail Client. - `in_reply_to_mail_name` (str | None): Optional reference ID for the email being replied to. - `custom_headers` (dict | None): Optional custom headers. - `attachments` (list[dict] | None): List of attachments. +- `is_newsletter` (bool): Optional flag to mark the email as a newsletter. Defaults to False. **Response:** Returns a UUID (name) of the created Outgoing Mail. @@ -242,6 +243,7 @@ These endpoints facilitate sending emails from the Frappe Mail Client. - `from_` (str): Sender's email address. - `to` (str | list[str]): Recipient email(s). - `raw_message` (str): The complete raw MIME message. +- `is_newsletter` (bool): Optional flag to mark the email as a newsletter. Defaults to False. **Response:** Returns the UUID (name) of the created Outgoing Mail. @@ -251,72 +253,6 @@ These endpoints facilitate sending emails from the Frappe Mail Client. { "message": "019300a4-91fc-741f-9fe5-9ade8976637f" } ``` -#### 2.3 Send Batch - -**Endpoint:** `POST /outbound/send-batch` or `/api/method/mail.api.outbound.send_batch` - -**Description:** Sends multiple email messages in a batch. - -**Parameters:** JSON array of email details. Each entry contains `from_`, `to`, `subject`, etc., similar to `/outbound/send`. - -**Response:** Returns a list of UUIDs (names) for the created Outgoing Mails. - -**Example Response:** - -```json -{ - "message": [ - "019300a4-91fc-741f-9fe5-9ade8976637f", - "019300a5-2062-7267-8966-603606f9b41b", - "01930081-3b5b-7ad4-a837-ff27f55bcbac" - ] -} -``` - -#### 2.4 Send Raw Batch - -**Endpoint:** `POST /outbound/send-raw-batch` or `/api/method/mail.api.outbound.send_raw_batch` - -**Description:** Sends multiple raw MIME messages in a batch. - -**Parameters:** JSON array of `from_`, `to`, `raw_message`, similar to `/outbound/send-raw`. - -**Response:** Returns a list of UUIDs (names) for the created Outgoing Mails. - -**Example Response:** - -```json -{ - "message": [ - "019300a4-91fc-741f-9fe5-9ade8976637f", - "019300a5-2062-7267-8966-603606f9b41b", - "01930081-3b5b-7ad4-a837-ff27f55bcbac" - ] -} -``` - -#### 2.5 Send Newsletter - -**Endpoint:** `POST /outbound/send-newsletter` or `/api/method/mail.api.outbound.send_newsletter` - -**Description:** Sends low-priority newsletters. Works the same as batch email sending but with a priority setting. - -**Parameters:** JSON array of newsletter details similar to `/outbound/send-batch` or `/outbound/send-raw-batch`. - -**Response:** Returns a list of UUIDs (names) for the created newsletter Outgoing Mails. - -**Example Response:** - -```json -{ - "message": [ - "019300a4-91fc-741f-9fe5-9ade8976637f", - "019300a5-2062-7267-8966-603606f9b41b", - "01930081-3b5b-7ad4-a837-ff27f55bcbac" - ] -} -``` - ### 3. Inbound API These endpoints are for retrieving received emails. diff --git a/mail_client/api/outbound.py b/mail_client/api/outbound.py index 2f8d96d5..5efe4291 100644 --- a/mail_client/api/outbound.py +++ b/mail_client/api/outbound.py @@ -1,8 +1,8 @@ -import json from email.utils import parseaddr import frappe from frappe import _ +from frappe.utils import cint from mail_client.mail_client.doctype.outgoing_mail.outgoing_mail import create_outgoing_mail @@ -20,6 +20,7 @@ def send( in_reply_to_mail_name: str | None = None, custom_headers: dict | None = None, attachments: list[dict] | None = None, + is_newsletter: bool = False, ) -> str: """Send Mail.""" @@ -38,6 +39,7 @@ def send( custom_headers=custom_headers, attachments=attachments, via_api=1, + is_newsletter=cint(is_newsletter), ) return doc.name @@ -47,107 +49,58 @@ def send( def send_raw( from_: str, to: str | list[str], - raw_message: str, + raw_message: str | None = None, + is_newsletter: bool = False, ) -> str: """Send Raw Mail.""" display_name, sender = parseaddr(from_) + raw_message = raw_message or get_message_from_files() + if not raw_message: + frappe.throw(_("The raw message is required."), frappe.MandatoryError) + doc = create_outgoing_mail( sender=sender, to=to, display_name=display_name, raw_message=raw_message, via_api=1, + is_newsletter=cint(is_newsletter), ) return doc.name +# TODO: Remove @frappe.whitelist(methods=["POST"]) -def send_batch() -> list[str]: - """Send Mails in Batch.""" - - mails = json.loads(frappe.request.data.decode()) - validate_batch(mails, mandatory_fields=["from_", "to", "subject"]) - - documents = [] - for mail in mails: - mail = get_mail_dict(mail) - doc = create_outgoing_mail(**mail) - documents.append(doc.name) - - return documents - - -@frappe.whitelist(methods=["POST"]) -def send_raw_batch() -> list[str]: - """Send Raw Mails in Batch.""" - - mails = json.loads(frappe.request.data.decode()) - validate_batch(mails, mandatory_fields=["from_", "to", "raw_message"]) - - documents = [] - for mail in mails: - mail = get_mail_dict(mail) - doc = create_outgoing_mail(**mail) - documents.append(doc.name) - - return documents - - -@frappe.whitelist(methods=["POST"]) -def send_newsletter() -> None: +def send_newsletter( + from_: str, + to: str | list[str], + raw_message: str | None = None, +) -> str: """Send Newsletter.""" - mails = json.loads(frappe.request.data.decode()) - - if isinstance(mails, dict): - mails = [mails] - - validate_batch(mails, mandatory_fields=["from_", "to"]) - - for mail in mails: - mail = get_mail_dict(mail) - mail["is_newsletter"] = 1 - create_outgoing_mail(**mail) - - -def validate_batch(mails: list[dict], mandatory_fields: list[str]) -> None: - """Validates the batch data.""" - - if len(mails) > 100: - raise frappe.ValidationError("Batch size cannot exceed 100.") + display_name, sender = parseaddr(from_) + raw_message = raw_message or get_message_from_files() + if not raw_message: + frappe.throw(_("The raw message is required."), frappe.MandatoryError) - for mail in mails: - for field in mandatory_fields: - if not mail.get(field): - raise frappe.ValidationError(f"{field} is mandatory.") + doc = create_outgoing_mail( + sender=sender, + to=to, + display_name=display_name, + raw_message=raw_message, + via_api=1, + is_newsletter=1, + ) + return doc.name -def get_mail_dict(data: dict) -> dict: - """Returns the mail dict.""" - display_name, sender = parseaddr(data["from_"]) - mail = { - "sender": sender, - "to": data["to"], - "display_name": display_name, - "via_api": 1, - } +def get_message_from_files() -> str | None: + """Returns the message from the files""" - if data.get("raw_message"): - mail["raw_message"] = data["raw_message"] - else: - mail.update( - { - "subject": data["subject"], - "cc": data.get("cc"), - "bcc": data.get("bcc"), - "body_html": data.get("html"), - "reply_to": data.get("reply_to"), - "custom_headers": data.get("headers"), - "attachments": data.get("attachments"), - } - ) + files = frappe._dict(frappe.request.files) - return mail + if files and files.get("raw_message"): + return files["raw_message"].read().decode("utf-8") diff --git a/mail_client/hooks.py b/mail_client/hooks.py index 000b3dc2..c56885f4 100644 --- a/mail_client/hooks.py +++ b/mail_client/hooks.py @@ -23,21 +23,6 @@ "target": "/api/method/mail_client.api.outbound.send_raw", "redirect_http_status": 307, }, - { - "source": "/outbound/send-batch", - "target": "/api/method/mail_client.api.outbound.send_batch", - "redirect_http_status": 307, - }, - { - "source": "/outbound/send-raw-batch", - "target": "/api/method/mail_client.api.outbound.send_raw_batch", - "redirect_http_status": 307, - }, - { - "source": "/outbound/send-newsletter", - "target": "/api/method/mail_client.api.outbound.send_newsletter", - "redirect_http_status": 307, - }, { "source": "/inbound/pull", "target": "/api/method/mail_client.api.inbound.pull",