1+ from django .conf import settings
2+ from django .utils .encoding import force_str
3+
14from ..exceptions import AnymailRequestsAPIError
25from ..message import AnymailRecipientStatus
36from ..utils import get_anymail_setting , update_deep
@@ -86,6 +89,7 @@ def get_api_endpoint(self):
8689
8790 def serialize_data (self ):
8891 self ._finalize_recipients ()
92+ self ._check_content_options ()
8993 return self .serialize_json (self .data )
9094
9195 def _finalize_recipients (self ):
@@ -126,6 +130,31 @@ def _finalize_recipients(self):
126130 for email in self .cc_and_bcc
127131 )
128132
133+ # SparkPost silently ignores certain "content" payload fields
134+ # when a template_id is used.
135+ IGNORED_WITH_TEMPLATE_ID = {
136+ # SparkPost API content.<field> -> feature name (for error message)
137+ "attachments" : "attachments" ,
138+ "inline_images" : "inline images" ,
139+ "headers" : "extra headers and/or cc recipients" ,
140+ "from" : "from_email" ,
141+ "reply_to" : "reply_to" ,
142+ }
143+
144+ def _check_content_options (self ):
145+ if "template_id" in self .data ["content" ]:
146+ # subject, text, and html will cause 422 API Error:
147+ # "message": "Both content object and template_id are specified",
148+ # "code": "1301"
149+ # but others are silently ignored in a template send:
150+ ignored = [
151+ feature_name
152+ for field , feature_name in self .IGNORED_WITH_TEMPLATE_ID .items ()
153+ if field in self .data ["content" ]
154+ ]
155+ if ignored :
156+ self .unsupported_feature ("template_id with %s" % ", " .join (ignored ))
157+
129158 #
130159 # Payload construction
131160 #
@@ -138,7 +167,8 @@ def init_payload(self):
138167 }
139168
140169 def set_from_email (self , email ):
141- self .data ["content" ]["from" ] = email .address
170+ if email :
171+ self .data ["content" ]["from" ] = email .address
142172
143173 def set_to (self , emails ):
144174 if emails :
@@ -293,13 +323,22 @@ def set_track_opens(self, track_opens):
293323
294324 def set_template_id (self , template_id ):
295325 self .data ["content" ]["template_id" ] = template_id
296- # Must remove empty string "content" params when using stored template
326+ # Must remove empty string "content" params when using stored template.
327+ # (Non-empty params are left in place, to cause API error.)
297328 for content_param in ["subject" , "text" , "html" ]:
298329 try :
299330 if not self .data ["content" ][content_param ]:
300331 del self .data ["content" ][content_param ]
301332 except KeyError :
302333 pass
334+ # "from" is also silently ignored. Strip it if empty or DEFAULT_FROM_EMAIL,
335+ # else leave in place to cause error in _check_content_options.
336+ try :
337+ from_email = self .data ["content" ]["from" ]
338+ if not from_email or from_email == force_str (settings .DEFAULT_FROM_EMAIL ):
339+ del self .data ["content" ]["from" ]
340+ except KeyError :
341+ pass
303342
304343 def set_merge_data (self , merge_data ):
305344 for recipient in self .data ["recipients" ]:
0 commit comments