@@ -117,6 +117,7 @@ def __init__(self, message, defaults, backend, *args, **kwargs):
117117 self .merge_global_data = {}
118118 self .metadata = {}
119119 self .merge_metadata = {}
120+ self .merge_headers = {}
120121 self .to_emails = []
121122
122123 super ().__init__ (message , defaults , backend , auth = auth , * args , ** kwargs )
@@ -191,6 +192,8 @@ def serialize_data(self):
191192 # (E.g., Mailgun's custom-data "name" is set to "%recipient.name%", which picks
192193 # up its per-recipient value from Mailgun's
193194 # `recipient-variables[to_email]["name"]`.)
195+ # (6) Anymail's `merge_headers` (per-recipient headers) maps to recipient-variables
196+ # prepended with 'h:'.
194197 #
195198 # If Anymail's `merge_data`, `template_id` (stored templates) and `metadata` (or
196199 # `merge_metadata`) are used together, there's a possibility of conflicting keys
@@ -268,6 +271,40 @@ def vkey(key): # 'v:key'
268271 {key : "%recipient.{}%" .format (key ) for key in merge_data_keys }
269272 )
270273
274+ # (6) merge_headers --> Mailgun recipient_variables via 'h:'-prefixed keys
275+ if self .merge_headers :
276+
277+ def hkey (field_name ): # 'h:Field-Name'
278+ return "h:{}" .format (field_name .title ())
279+
280+ merge_header_fields = flatset (
281+ recipient_headers .keys ()
282+ for recipient_headers in self .merge_headers .values ()
283+ )
284+ merge_header_defaults = {
285+ # existing h:Field-Name value (from extra_headers), or empty string
286+ field : self .data .get (hkey (field ), "" )
287+ for field in merge_header_fields
288+ }
289+ self .data .update (
290+ # Set up 'h:Field-Name': '%recipient.h:Field-Name%' indirection
291+ {
292+ hvar : f"%recipient.{ hvar } %"
293+ for hvar in [hkey (field ) for field in merge_header_fields ]
294+ }
295+ )
296+
297+ for email in self .to_emails :
298+ # Each recipient's recipient_variables needs _all_ merge header fields
299+ recipient_headers = merge_header_defaults .copy ()
300+ recipient_headers .update (self .merge_headers .get (email , {}))
301+ recipient_variables_for_headers = {
302+ hkey (field ): value for field , value in recipient_headers .items ()
303+ }
304+ recipient_variables .setdefault (email , {}).update (
305+ recipient_variables_for_headers
306+ )
307+
271308 # populate Mailgun params
272309 self .data .update ({"v:%s" % key : value for key , value in custom_data .items ()})
273310 if recipient_variables or self .is_batch ():
@@ -308,8 +345,8 @@ def set_reply_to(self, emails):
308345 self .data ["h:Reply-To" ] = reply_to
309346
310347 def set_extra_headers (self , headers ):
311- for key , value in headers .items ():
312- self .data ["h:%s" % key ] = value
348+ for field , value in headers .items ():
349+ self .data ["h:%s" % field . title () ] = value
313350
314351 def set_text_body (self , body ):
315352 self .data ["text" ] = body
@@ -385,6 +422,9 @@ def set_merge_metadata(self, merge_metadata):
385422 # Processed at serialization time (to allow combining with merge_data)
386423 self .merge_metadata = merge_metadata
387424
425+ def set_merge_headers (self , merge_headers ):
426+ self .merge_headers = merge_headers
427+
388428 def set_esp_extra (self , extra ):
389429 self .data .update (extra )
390430 # Allow override of sender_domain via esp_extra
0 commit comments