1
+ import os
1
2
from email .utils import parseaddr
2
3
3
4
import frappe
4
5
from frappe import _
5
6
from frappe .utils .file_manager import save_file
6
7
from werkzeug .datastructures .file_storage import FileStorage
8
+ from werkzeug .utils import secure_filename
7
9
8
10
from mail .mail .doctype .mail_queue .mail_queue import MailQueue
11
+ from mail .utils import get_messages_directory
9
12
from mail .utils .cache import get_account_for_user
10
13
from mail .utils .rate_limiter import dynamic_rate_limit
11
14
from mail .utils .user import has_role
@@ -98,25 +101,22 @@ def send_raw(
98
101
raw_message : str | None = None ,
99
102
is_newsletter : bool = False ,
100
103
) -> str :
101
- """Send Raw Mail."""
104
+ """Send raw email. Supports both single-shot and chunked upload."""
105
+
106
+ chunk_index = frappe .form_dict .get ("chunk_index" )
107
+ total_chunks = frappe .form_dict .get ("total_chunk_count" )
108
+ upload_session = frappe .form_dict .get ("uuid" )
109
+
110
+ if chunk_index is not None and total_chunks is not None and upload_session :
111
+ return _handle_chunked_upload (
112
+ from_ , to , is_newsletter , int (chunk_index ), int (total_chunks ), str (upload_session )
113
+ )
102
114
103
- from_name , from_email = parseaddr (from_ )
104
115
raw_message = raw_message or get_message_from_files ()
105
116
if not raw_message :
106
117
frappe .throw (_ ("The raw message is required." ), frappe .MandatoryError )
107
118
108
- doc = MailQueue ._create (
109
- account = get_account (),
110
- from_name = from_name ,
111
- from_email = from_email ,
112
- recipients = format_recipients (to ),
113
- via_api = True ,
114
- newsletter = is_newsletter ,
115
- raw_message = raw_message ,
116
- delivery_mode = "Batch" if is_newsletter else "Enqueue" ,
117
- )
118
-
119
- return doc .name
119
+ return _enqueue_mail (from_ , to , raw_message , is_newsletter )
120
120
121
121
122
122
def get_account () -> str :
@@ -131,7 +131,7 @@ def get_account() -> str:
131
131
132
132
133
133
def get_message_from_files () -> str | None :
134
- """Returns the message from the files """
134
+ """Extracts message from uploaded file in single upload mode. """
135
135
136
136
files = frappe ._dict (frappe .request .files )
137
137
@@ -164,6 +164,35 @@ def format_reply_to(reply_to: str | list[str] | None) -> list[dict]:
164
164
return _normalize_recipients (reply_to )
165
165
166
166
167
+ def _handle_chunked_upload (
168
+ from_ : str , to : str | list [str ], is_newsletter : bool , chunk_index : int , total_chunks : int , session_id : str
169
+ ) -> str :
170
+ """Handle chunked uploads for large emails."""
171
+
172
+ file = frappe .request .files .get ("raw_message" )
173
+ if not file :
174
+ frappe .throw (_ ("No file part named 'raw_message' found." ))
175
+
176
+ upload_dir = get_messages_directory ()
177
+ filename = secure_filename (f"{ session_id } .eml" )
178
+ temp_path = os .path .join (upload_dir , filename )
179
+ offset = int (frappe .form_dict .get ("chunk_byte_offset" , 0 ))
180
+
181
+ with open (temp_path , "ab" ) as f :
182
+ f .seek (offset )
183
+ f .write (file .stream .read ())
184
+
185
+ if chunk_index < total_chunks - 1 :
186
+ return f"Chunk { chunk_index + 1 } of { total_chunks } received."
187
+
188
+ with open (temp_path , "rb" ) as f :
189
+ raw_message = f .read ().decode ("utf-8" )
190
+
191
+ os .remove (temp_path )
192
+
193
+ return _enqueue_mail (from_ , to , raw_message , is_newsletter )
194
+
195
+
167
196
def _normalize_recipients (
168
197
recipients : str | list [str ] | None , recipient_type : str | None = None
169
198
) -> list [dict ]:
@@ -181,3 +210,24 @@ def _normalize_recipients(
181
210
result .append (recipient_dict )
182
211
183
212
return result
213
+
214
+
215
+ def _enqueue_mail (from_ : str , to : str | list [str ], raw_message : str , is_newsletter : bool = False ) -> str :
216
+ """Enqueue mail in MailQueue."""
217
+
218
+ from_name , from_email = parseaddr (from_ )
219
+ if not raw_message :
220
+ frappe .throw (_ ("The raw message is required." ), frappe .MandatoryError )
221
+
222
+ doc = MailQueue ._create (
223
+ account = get_account (),
224
+ from_name = from_name ,
225
+ from_email = from_email ,
226
+ recipients = format_recipients (to ),
227
+ via_api = True ,
228
+ newsletter = is_newsletter ,
229
+ raw_message = raw_message ,
230
+ delivery_mode = "Batch" if is_newsletter else "Enqueue" ,
231
+ )
232
+
233
+ return doc .name
0 commit comments