3
3
from email .header import decode_header , make_header
4
4
from email .headerregistry import Address
5
5
6
+ from ..exceptions import AnymailRequestsAPIError
6
7
from ..message import AnymailRecipientStatus
7
8
from ..utils import (
8
9
BASIC_NUMERIC_TYPES ,
@@ -56,10 +57,24 @@ def build_message_payload(self, message, defaults):
56
57
return ResendPayload (message , defaults , self )
57
58
58
59
def parse_recipient_status (self , response , payload , message ):
59
- # Resend provides single message id, no other information.
60
- # Assume "queued".
61
60
parsed_response = self .deserialize_json_response (response , payload , message )
62
- message_id = parsed_response ["id" ]
61
+ try :
62
+ message_id = parsed_response ["id" ]
63
+ message_ids = None
64
+ except (KeyError , TypeError ):
65
+ # Batch send?
66
+ try :
67
+ message_id = None
68
+ message_ids = [item ["id" ] for item in parsed_response ["data" ]]
69
+ except (KeyError , TypeError ) as err :
70
+ raise AnymailRequestsAPIError (
71
+ "Invalid Resend API response format" ,
72
+ email_message = message ,
73
+ payload = payload ,
74
+ response = response ,
75
+ backend = self ,
76
+ ) from err
77
+
63
78
recipient_status = CaseInsensitiveCasePreservingDict (
64
79
{
65
80
recip .addr_spec : AnymailRecipientStatus (
@@ -68,23 +83,55 @@ def parse_recipient_status(self, response, payload, message):
68
83
for recip in payload .recipients
69
84
}
70
85
)
86
+ if message_ids :
87
+ # batch send: ids are in same order as to_recipients
88
+ for recip , message_id in zip (payload .to_recipients , message_ids ):
89
+ recipient_status [recip .addr_spec ] = AnymailRecipientStatus (
90
+ message_id = message_id , status = "queued"
91
+ )
71
92
return dict (recipient_status )
72
93
73
94
74
95
class ResendPayload (RequestsPayload ):
75
96
def __init__ (self , message , defaults , backend , * args , ** kwargs ):
76
97
self .recipients = [] # for parse_recipient_status
98
+ self .to_recipients = [] # for parse_recipient_status
99
+ self .metadata = {}
100
+ self .merge_metadata = {}
77
101
headers = kwargs .pop ("headers" , {})
78
102
headers ["Authorization" ] = "Bearer %s" % backend .api_key
79
103
headers ["Content-Type" ] = "application/json"
80
104
headers ["Accept" ] = "application/json"
81
105
super ().__init__ (message , defaults , backend , headers = headers , * args , ** kwargs )
82
106
83
107
def get_api_endpoint (self ):
108
+ if self .is_batch ():
109
+ return "emails/batch"
84
110
return "emails"
85
111
86
112
def serialize_data (self ):
87
- return self .serialize_json (self .data )
113
+ payload = self .data
114
+ if self .is_batch ():
115
+ # Burst payload across to addresses
116
+ to_emails = self .data .pop ("to" , [])
117
+ payload = []
118
+ for to_email , to in zip (to_emails , self .to_recipients ):
119
+ data = self .data .copy ()
120
+ data ["to" ] = [to_email ] # formatted for Resend (w/ workarounds)
121
+ if to .addr_spec in self .merge_metadata :
122
+ # Merge global metadata with any per-recipient metadata.
123
+ recipient_metadata = self .metadata .copy ()
124
+ recipient_metadata .update (self .merge_metadata [to .addr_spec ])
125
+ if "headers" in data :
126
+ data ["headers" ] = data ["headers" ].copy ()
127
+ else :
128
+ data ["headers" ] = {}
129
+ data ["headers" ]["X-Metadata" ] = self .serialize_json (
130
+ recipient_metadata
131
+ )
132
+ payload .append (data )
133
+
134
+ return self .serialize_json (payload )
88
135
89
136
#
90
137
# Payload construction
@@ -147,6 +194,8 @@ def set_recipients(self, recipient_type, emails):
147
194
field = recipient_type
148
195
self .data [field ] = [self ._resend_email_address (email ) for email in emails ]
149
196
self .recipients += emails
197
+ if recipient_type == "to" :
198
+ self .to_recipients = emails
150
199
151
200
def set_subject (self , subject ):
152
201
self .data ["subject" ] = subject
@@ -206,6 +255,7 @@ def set_metadata(self, metadata):
206
255
self .data .setdefault ("headers" , {})["X-Metadata" ] = self .serialize_json (
207
256
metadata
208
257
)
258
+ self .metadata = metadata # may be needed for batch send in serialize_data
209
259
210
260
# Resend doesn't support delayed sending
211
261
# def set_send_at(self, send_at):
@@ -223,9 +273,16 @@ def set_tags(self, tags):
223
273
# (Their template feature is rendered client-side,
224
274
# using React in node.js.)
225
275
# def set_template_id(self, template_id):
226
- # def set_merge_data(self, merge_data):
227
276
# def set_merge_global_data(self, merge_global_data):
228
- # def set_merge_metadata(self, merge_metadata):
277
+
278
+ def set_merge_data (self , merge_data ):
279
+ # Empty merge_data is a request to use batch send;
280
+ # any other merge_data is unsupported.
281
+ if any (recipient_data for recipient_data in merge_data .values ()):
282
+ self .unsupported_feature ("merge_data" )
283
+
284
+ def set_merge_metadata (self , merge_metadata ):
285
+ self .merge_metadata = merge_metadata # late bound in serialize_data
229
286
230
287
def set_esp_extra (self , extra ):
231
288
self .data .update (extra )
0 commit comments