I figured it out! Turns out, the APNS payload has to be encoded as a string within the larger payload - and it totally works.
Here's the final, working code:
apns_dict = {'aps':{'alert':'inner message','sound':'mySound.caf'}}
apns_string = json.dumps(apns_dict,ensure_ascii=False)
message = {'default':'default message','APNS_SANDBOX':apns_string}
messageJSON = json.dumps(message,ensure_ascii=False)
sns.publish(message=messageJSON,target_arn=device_arn,message_structure='json')
Here's a walkthrough of what's going on in this code:
First, create the python dictionary for APNS:
apns_dict = {'aps':{'alert':'inner message','sound':'mySound.caf'}}
Second, take that dictionary, and turn it into a JSON-formatted string:
apns_string = json.dumps(apns_dict,ensure_ascii=False)
Third, put that string into the larger payload:
message = {'default':'default message','APNS_SANDBOX':apns_string}
Next, we encode that in its own JSON-formatted string:
messageJSON = json.dumps(message,ensure_ascii=False)
The resulting string can then be published using boto:
sns.publish(message=messageJSON,target_arn=device_arn,message_structure='json')