SendGrid: change message_id from Message-ID/smtp-id to UUID anymail_id

SendGrid does not always correctly provide the sent Message-ID header value 
to a tracking webhook's smtp-id field, making it unreliable to use for Anymail's 
`message_id`.

Instead, generate a UUID `message_id` for Anymail tracking, and pass it from 
send to webhooks in SendGrid custom args as anymail_id.

Webhooks will fall back to smtp-id for compatibility with previously-sent 
messages that didn't have an anymail_id custom arg.

Fixes #108
This commit is contained in:
Josh Kersey
2018-05-30 13:52:36 -05:00
committed by Mike Edmunds
parent 51d2a404c0
commit d8d1407c61
7 changed files with 52 additions and 86 deletions

View File

@@ -1,7 +1,7 @@
import uuid
from email.utils import quote as rfc822_quote from email.utils import quote as rfc822_quote
import warnings import warnings
from django.core.mail import make_msgid
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from .base_requests import AnymailRequestsBackend, RequestsPayload from .base_requests import AnymailRequestsBackend, RequestsPayload
@@ -99,7 +99,7 @@ class SendGridPayload(RequestsPayload):
"""Performs any necessary serialization on self.data, and returns the result.""" """Performs any necessary serialization on self.data, and returns the result."""
if self.generate_message_id: if self.generate_message_id:
self.ensure_message_id() self.set_anymail_id()
self.build_merge_data() self.build_merge_data()
if not self.data["headers"]: if not self.data["headers"]:
@@ -107,28 +107,11 @@ class SendGridPayload(RequestsPayload):
return self.serialize_json(self.data) return self.serialize_json(self.data)
def ensure_message_id(self): def set_anymail_id(self):
"""Ensure message has a known Message-ID for later event tracking""" """Ensure message has a known anymail_id for later event tracking"""
if "Message-ID" not in self.data["headers"]:
# Only make our own if caller hasn't already provided one
self.data["headers"]["Message-ID"] = self.make_message_id()
self.message_id = self.data["headers"]["Message-ID"]
# Workaround for missing message ID (smtp-id) in SendGrid engagement events self.message_id = str(uuid.uuid4())
# (click and open tracking): because unique_args get merged into the raw event self.data.setdefault("custom_args", {})["anymail_id"] = self.message_id
# record, we can supply the 'smtp-id' field for any events missing it.
self.data.setdefault("custom_args", {})["smtp-id"] = self.message_id
def make_message_id(self):
"""Returns a Message-ID that could be used for this payload
Tries to use the from_email's domain as the Message-ID's domain
"""
try:
_, domain = self.data["from"]["email"].split("@")
except (AttributeError, KeyError, TypeError, ValueError):
domain = None
return make_msgid(domain=domain)
def build_merge_data(self): def build_merge_data(self):
"""Set personalizations[...]['substitutions'] and data['sections']""" """Set personalizations[...]['substitutions'] and data['sections']"""

View File

@@ -1,6 +1,6 @@
import uuid
import warnings import warnings
from django.core.mail import make_msgid
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning
@@ -99,7 +99,7 @@ class SendGridPayload(RequestsPayload):
"""Performs any necessary serialization on self.data, and returns the result.""" """Performs any necessary serialization on self.data, and returns the result."""
if self.generate_message_id: if self.generate_message_id:
self.ensure_message_id() self.set_anymail_id()
self.build_merge_data() self.build_merge_data()
if self.merge_data is not None: if self.merge_data is not None:
@@ -136,29 +136,11 @@ class SendGridPayload(RequestsPayload):
return self.data return self.data
def ensure_message_id(self): def set_anymail_id(self):
"""Ensure message has a known Message-ID for later event tracking""" """Ensure message has a known anymail_id for later event tracking"""
headers = self.data["headers"]
if "Message-ID" not in headers:
# Only make our own if caller hasn't already provided one
headers["Message-ID"] = self.make_message_id()
self.message_id = headers["Message-ID"]
# Workaround for missing message ID (smtp-id) in SendGrid engagement events self.message_id = str(uuid.uuid4())
# (click and open tracking): because unique_args get merged into the raw event self.smtpapi.setdefault('unique_args', {})["anymail_id"] = self.message_id
# record, we can supply the 'smtp-id' field for any events missing it.
self.smtpapi.setdefault('unique_args', {})['smtp-id'] = self.message_id
def make_message_id(self):
"""Returns a Message-ID that could be used for this payload
Tries to use the from_email's domain as the Message-ID's domain
"""
try:
_, domain = self.data["from"].split("@")
except (AttributeError, KeyError, TypeError, ValueError):
domain = None
return make_msgid(domain=domain)
def build_merge_data(self): def build_merge_data(self):
"""Set smtpapi['sub'] and ['section']""" """Set smtpapi['sub'] and ['section']"""

View File

@@ -72,7 +72,7 @@ class SendGridTrackingWebhookView(AnymailBaseWebhookView):
return AnymailTrackingEvent( return AnymailTrackingEvent(
event_type=event_type, event_type=event_type,
timestamp=timestamp, timestamp=timestamp,
message_id=esp_event.get('smtp-id', None), message_id=esp_event.get('anymail_id', esp_event.get('smtp-id')), # backwards compatibility
event_id=esp_event.get('sg_event_id', None), event_id=esp_event.get('sg_event_id', None),
recipient=esp_event.get('email', None), recipient=esp_event.get('email', None),
reject_reason=reject_reason, reject_reason=reject_reason,
@@ -86,6 +86,7 @@ class SendGridTrackingWebhookView(AnymailBaseWebhookView):
# Known keys in SendGrid events (used to recover metadata above) # Known keys in SendGrid events (used to recover metadata above)
sendgrid_event_keys = { sendgrid_event_keys = {
'anymail_id',
'asm_group_id', 'asm_group_id',
'attempt', # MTA deferred count 'attempt', # MTA deferred count
'category', 'category',

View File

@@ -55,10 +55,8 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.assertEqual(data['personalizations'], [{ self.assertEqual(data['personalizations'], [{
'to': [{'email': "to@example.com"}], 'to': [{'email': "to@example.com"}],
}]) }])
# make sure backend assigned a Message-ID for event tracking # make sure the backend assigned the anymail_id for event tracking and notification
self.assertRegex(data['headers']['Message-ID'], r'\<.+@sender\.example\.com\>') # id uses from_email's domain self.assertUUIDIsValid(data['custom_args']['anymail_id'])
# make sure we added the Message-ID to custom_args for event notification
self.assertEqual(data['headers']['Message-ID'], data['custom_args']['smtp-id'])
def test_name_addr(self): def test_name_addr(self):
"""Make sure RFC2822 name-addr format (with display-name) is allowed """Make sure RFC2822 name-addr format (with display-name) is allowed
@@ -119,9 +117,7 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
'Message-ID': "<mycustommsgid@sales.example.com>", 'Message-ID': "<mycustommsgid@sales.example.com>",
}) })
# make sure custom Message-ID also added to custom_args # make sure custom Message-ID also added to custom_args
self.assertEqual(data['custom_args'], { self.assertUUIDIsValid(data['custom_args']['anymail_id'])
'smtp-id': "<mycustommsgid@sales.example.com>",
})
def test_html_message(self): def test_html_message(self):
text_content = 'This is an important message.' text_content = 'This is an important message.'
@@ -345,7 +341,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
self.message.metadata = {'user_id': "12345", 'items': 6, 'float': 98.6, 'long': longtype(123)} self.message.metadata = {'user_id': "12345", 'items': 6, 'float': 98.6, 'long': longtype(123)}
self.message.send() self.message.send()
data = self.get_api_call_json() data = self.get_api_call_json()
data['custom_args'].pop('smtp-id', None) # remove Message-ID we added as tracking workaround data['custom_args'].pop('anymail_id', None) # remove Message-ID we added as tracking workaround
self.assertEqual(data['custom_args'], {'user_id': "12345", self.assertEqual(data['custom_args'], {'user_id': "12345",
'items': "6", # int converted to a string, 'items': "6", # int converted to a string,
'float': "98.6", # float converted to a string (watch binary rounding!) 'float': "98.6", # float converted to a string (watch binary rounding!)
@@ -579,7 +575,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
sent = msg.send() sent = msg.send()
self.assertEqual(sent, 1) self.assertEqual(sent, 1)
self.assertEqual(msg.anymail_status.status, {'queued'}) self.assertEqual(msg.anymail_status.status, {'queued'})
self.assertRegex(msg.anymail_status.message_id, r'\<.+@example\.com\>') # don't know exactly what it'll be self.assertUUIDIsValid(msg.anymail_status.message_id) # don't know exactly what it'll be
self.assertEqual(msg.anymail_status.recipients['to1@example.com'].status, 'queued') self.assertEqual(msg.anymail_status.recipients['to1@example.com'].status, 'queued')
self.assertEqual(msg.anymail_status.recipients['to1@example.com'].message_id, self.assertEqual(msg.anymail_status.recipients['to1@example.com'].message_id,
msg.anymail_status.message_id) msg.anymail_status.message_id)

View File

@@ -63,12 +63,9 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.assertEqual(data['text'], "Here is the message.") self.assertEqual(data['text'], "Here is the message.")
self.assertEqual(data['from'], "from@sender.example.com") self.assertEqual(data['from'], "from@sender.example.com")
self.assertEqual(data['to'], ["to@example.com"]) self.assertEqual(data['to'], ["to@example.com"])
# make sure backend assigned a Message-ID for event tracking # make sure the backend assigned the anymail_id to unique_args for event tracking and notification
email_headers = json.loads(data['headers'])
self.assertRegex(email_headers['Message-ID'], r'\<.+@sender\.example\.com\>') # id uses from_email's domain
# make sure we added the Message-ID to unique_args for event notification
smtpapi = self.get_smtpapi() smtpapi = self.get_smtpapi()
self.assertEqual(email_headers['Message-ID'], smtpapi['unique_args']['smtp-id']) self.assertUUIDIsValid(smtpapi['unique_args']['anymail_id'])
@override_settings(ANYMAIL={'SENDGRID_USERNAME': 'sg_username', 'SENDGRID_PASSWORD': 'sg_password'}) @override_settings(ANYMAIL={'SENDGRID_USERNAME': 'sg_username', 'SENDGRID_PASSWORD': 'sg_password'})
def test_user_pass_auth(self): def test_user_pass_auth(self):
@@ -129,10 +126,9 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
'Reply-To': 'another@example.com', 'Reply-To': 'another@example.com',
'X-MyHeader': 'my value', 'X-MyHeader': 'my value',
}) })
# make sure custom Message-ID also added to unique_args # make sure anymail_id also added to unique_args
self.assertJSONEqual(data['x-smtpapi'], { smtpapi_json = json.loads(data['x-smtpapi'])
'unique_args': {'smtp-id': '<mycustommsgid@sales.example.com>'} self.assertUUIDIsValid(smtpapi_json['unique_args']['anymail_id'])
})
def test_html_message(self): def test_html_message(self):
text_content = 'This is an important message.' text_content = 'This is an important message.'
@@ -293,8 +289,7 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.assertNotIn('ccname', data) self.assertNotIn('ccname', data)
self.assertNotIn('bcc', data) self.assertNotIn('bcc', data)
self.assertNotIn('bccname', data) self.assertNotIn('bccname', data)
headers = json.loads(data['headers']) self.assertNotIn('headers', data)
self.assertNotIn('Reply-To', headers)
# Test empty `to` -- but send requires at least one recipient somewhere (like cc) # Test empty `to` -- but send requires at least one recipient somewhere (like cc)
self.message.to = [] self.message.to = []
@@ -354,7 +349,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
self.message.metadata = {'user_id': "12345", 'items': 6} self.message.metadata = {'user_id': "12345", 'items': 6}
self.message.send() self.message.send()
smtpapi = self.get_smtpapi() smtpapi = self.get_smtpapi()
smtpapi['unique_args'].pop('smtp-id', None) # remove Message-ID we added as tracking workaround smtpapi['unique_args'].pop('anymail_id', None) # remove Message-ID we added as tracking workaround
self.assertEqual(smtpapi['unique_args'], {'user_id': "12345", 'items': 6}) self.assertEqual(smtpapi['unique_args'], {'user_id': "12345", 'items': 6})
def test_send_at(self): def test_send_at(self):
@@ -565,7 +560,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
sent = msg.send() sent = msg.send()
self.assertEqual(sent, 1) self.assertEqual(sent, 1)
self.assertEqual(msg.anymail_status.status, {'queued'}) self.assertEqual(msg.anymail_status.status, {'queued'})
self.assertRegex(msg.anymail_status.message_id, r'\<.+@example\.com\>') # don't know exactly what it'll be self.assertUUIDIsValid(msg.anymail_status.message_id)
self.assertEqual(msg.anymail_status.recipients['to1@example.com'].status, 'queued') self.assertEqual(msg.anymail_status.recipients['to1@example.com'].status, 'queued')
self.assertEqual(msg.anymail_status.recipients['to1@example.com'].message_id, self.assertEqual(msg.anymail_status.recipients['to1@example.com'].message_id,
msg.anymail_status.message_id) msg.anymail_status.message_id)

View File

@@ -23,7 +23,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
raw_events = [{ raw_events = [{
"email": "recipient@example.com", "email": "recipient@example.com",
"timestamp": 1461095246, "timestamp": 1461095246,
"smtp-id": "<wrfRRvF7Q0GgwUo2CvDmEA@example.com>", "anymail_id": "3c2f4df8-c6dd-4cd2-9b91-6582b81a0349",
"sg_event_id": "ZyjAM5rnQmuI1KFInHQ3Nw", "sg_event_id": "ZyjAM5rnQmuI1KFInHQ3Nw",
"sg_message_id": "wrfRRvF7Q0GgwUo2CvDmEA.filter0425p1mdw1.13037.57168B4A1D.0", "sg_message_id": "wrfRRvF7Q0GgwUo2CvDmEA.filter0425p1mdw1.13037.57168B4A1D.0",
"event": "processed", "event": "processed",
@@ -41,7 +41,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertEqual(event.event_type, "queued") self.assertEqual(event.event_type, "queued")
self.assertEqual(event.timestamp, datetime(2016, 4, 19, 19, 47, 26, tzinfo=utc)) self.assertEqual(event.timestamp, datetime(2016, 4, 19, 19, 47, 26, tzinfo=utc))
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<wrfRRvF7Q0GgwUo2CvDmEA@example.com>") self.assertEqual(event.message_id, "3c2f4df8-c6dd-4cd2-9b91-6582b81a0349")
self.assertEqual(event.event_id, "ZyjAM5rnQmuI1KFInHQ3Nw") self.assertEqual(event.event_id, "ZyjAM5rnQmuI1KFInHQ3Nw")
self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.tags, ["tag1", "tag2"]) self.assertEqual(event.tags, ["tag1", "tag2"])
@@ -57,7 +57,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
"event": "delivered", "event": "delivered",
"email": "recipient@example.com", "email": "recipient@example.com",
"timestamp": 1461095250, "timestamp": 1461095250,
"smtp-id": "<wrfRRvF7Q0GgwUo2CvDmEA@example.com>" "anymail_id": "4ab185c2-0171-492f-9ce0-27de258efc99"
}] }]
response = self.client.post('/anymail/sendgrid/tracking/', response = self.client.post('/anymail/sendgrid/tracking/',
content_type='application/json', data=json.dumps(raw_events)) content_type='application/json', data=json.dumps(raw_events))
@@ -69,7 +69,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertEqual(event.event_type, "delivered") self.assertEqual(event.event_type, "delivered")
self.assertEqual(event.timestamp, datetime(2016, 4, 19, 19, 47, 30, tzinfo=utc)) self.assertEqual(event.timestamp, datetime(2016, 4, 19, 19, 47, 30, tzinfo=utc))
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<wrfRRvF7Q0GgwUo2CvDmEA@example.com>") self.assertEqual(event.message_id, "4ab185c2-0171-492f-9ce0-27de258efc99")
self.assertEqual(event.event_id, "nOSv8m0eTQ-vxvwNwt3fZQ") self.assertEqual(event.event_id, "nOSv8m0eTQ-vxvwNwt3fZQ")
self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.mta_response, "250 2.0.0 OK 1461095248 m143si2210036ioe.159 - gsmtp ") self.assertEqual(event.mta_response, "250 2.0.0 OK 1461095248 m143si2210036ioe.159 - gsmtp ")
@@ -79,7 +79,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
def test_dropped_invalid_event(self): def test_dropped_invalid_event(self):
raw_events = [{ raw_events = [{
"email": "invalid@invalid", "email": "invalid@invalid",
"smtp-id": "<YZkwwo_vQUidhSh7sCzkvQ@example.com>", "anymail_id": "c74002d9-7ccb-4f67-8b8c-766cec03c9a6",
"timestamp": 1461095250, "timestamp": 1461095250,
"sg_event_id": "3NPOePGOTkeM_U3fgWApfg", "sg_event_id": "3NPOePGOTkeM_U3fgWApfg",
"sg_message_id": "filter0093p1las1.9128.5717FB8127.0", "sg_message_id": "filter0093p1las1.9128.5717FB8127.0",
@@ -95,7 +95,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "rejected") self.assertEqual(event.event_type, "rejected")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<YZkwwo_vQUidhSh7sCzkvQ@example.com>") self.assertEqual(event.message_id, "c74002d9-7ccb-4f67-8b8c-766cec03c9a6")
self.assertEqual(event.event_id, "3NPOePGOTkeM_U3fgWApfg") self.assertEqual(event.event_id, "3NPOePGOTkeM_U3fgWApfg")
self.assertEqual(event.recipient, "invalid@invalid") self.assertEqual(event.recipient, "invalid@invalid")
self.assertEqual(event.reject_reason, "invalid") self.assertEqual(event.reject_reason, "invalid")
@@ -104,7 +104,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
def test_dropped_unsubscribed_event(self): def test_dropped_unsubscribed_event(self):
raw_events = [{ raw_events = [{
"email": "unsubscribe@example.com", "email": "unsubscribe@example.com",
"smtp-id": "<Kwx3gAIKQOG7Nd5XEO7guQ@example.com>", "anymail_id": "a36ec0f9-aabe-45c7-9a84-3e17afb5cb65",
"timestamp": 1461095250, "timestamp": 1461095250,
"sg_event_id": "oxy9OLwMTAy5EsuZn1qhIg", "sg_event_id": "oxy9OLwMTAy5EsuZn1qhIg",
"sg_message_id": "filter0199p1las1.4745.5717FB6F5.0", "sg_message_id": "filter0199p1las1.4745.5717FB6F5.0",
@@ -120,7 +120,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "rejected") self.assertEqual(event.event_type, "rejected")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<Kwx3gAIKQOG7Nd5XEO7guQ@example.com>") self.assertEqual(event.message_id, "a36ec0f9-aabe-45c7-9a84-3e17afb5cb65")
self.assertEqual(event.event_id, "oxy9OLwMTAy5EsuZn1qhIg") self.assertEqual(event.event_id, "oxy9OLwMTAy5EsuZn1qhIg")
self.assertEqual(event.recipient, "unsubscribe@example.com") self.assertEqual(event.recipient, "unsubscribe@example.com")
self.assertEqual(event.reject_reason, "unsubscribed") self.assertEqual(event.reject_reason, "unsubscribed")
@@ -137,7 +137,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
"event": "bounce", "event": "bounce",
"email": "noreply@example.com", "email": "noreply@example.com",
"timestamp": 1461095250, "timestamp": 1461095250,
"smtp-id": "<Lli-03HcQ5-JLybO9fXsJg@example.com>", "anymail_id": "de212213-bb66-4302-8f3f-20acdb7a104e",
"type": "bounce" "type": "bounce"
}] }]
response = self.client.post('/anymail/sendgrid/tracking/', response = self.client.post('/anymail/sendgrid/tracking/',
@@ -149,7 +149,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "bounced") self.assertEqual(event.event_type, "bounced")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<Lli-03HcQ5-JLybO9fXsJg@example.com>") self.assertEqual(event.message_id, "de212213-bb66-4302-8f3f-20acdb7a104e")
self.assertEqual(event.event_id, "lC0Rc-FuQmKbnxCWxX1jRQ") self.assertEqual(event.event_id, "lC0Rc-FuQmKbnxCWxX1jRQ")
self.assertEqual(event.recipient, "noreply@example.com") self.assertEqual(event.recipient, "noreply@example.com")
self.assertEqual(event.mta_response, "550 5.1.1 The email account that you tried to reach does not exist.") self.assertEqual(event.mta_response, "550 5.1.1 The email account that you tried to reach does not exist.")
@@ -163,7 +163,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
"email": "recipient@example.com", "email": "recipient@example.com",
"attempt": "1", "attempt": "1",
"timestamp": 1461200990, "timestamp": 1461200990,
"smtp-id": "<20160421010427.2847.6797@example.com>", "anymail_id": "ccf83222-0d7e-4542-8beb-893122afa757",
}] }]
response = self.client.post('/anymail/sendgrid/tracking/', response = self.client.post('/anymail/sendgrid/tracking/',
content_type='application/json', data=json.dumps(raw_events)) content_type='application/json', data=json.dumps(raw_events))
@@ -174,7 +174,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "deferred") self.assertEqual(event.event_type, "deferred")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<20160421010427.2847.6797@example.com>") self.assertEqual(event.message_id, "ccf83222-0d7e-4542-8beb-893122afa757")
self.assertEqual(event.event_id, "b_syL5UiTvWC_Ky5L6Bs5Q") self.assertEqual(event.event_id, "b_syL5UiTvWC_Ky5L6Bs5Q")
self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.mta_response, self.assertEqual(event.mta_response,
@@ -187,7 +187,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
"ip": "66.102.6.229", "ip": "66.102.6.229",
"sg_event_id": "MjIwNDg5NTgtZGE3OC00NDI1LWFiMmMtMDUyZTU2ZmFkOTFm", "sg_event_id": "MjIwNDg5NTgtZGE3OC00NDI1LWFiMmMtMDUyZTU2ZmFkOTFm",
"sg_message_id": "wrfRRvF7Q0GgwUo2CvDmEA.filter0425p1mdw1.13037.57168B4A1D.0", "sg_message_id": "wrfRRvF7Q0GgwUo2CvDmEA.filter0425p1mdw1.13037.57168B4A1D.0",
"smtp-id": "<20160421010427.2847.6797@example.com>", "anymail_id": "44920b35-3e31-478b-bb67-b4f5e0c85ebc",
"useragent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0", "useragent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0",
"event": "open" "event": "open"
}] }]
@@ -200,7 +200,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "opened") self.assertEqual(event.event_type, "opened")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<20160421010427.2847.6797@example.com>") self.assertEqual(event.message_id, "44920b35-3e31-478b-bb67-b4f5e0c85ebc")
self.assertEqual(event.event_id, "MjIwNDg5NTgtZGE3OC00NDI1LWFiMmMtMDUyZTU2ZmFkOTFm") self.assertEqual(event.event_id, "MjIwNDg5NTgtZGE3OC00NDI1LWFiMmMtMDUyZTU2ZmFkOTFm")
self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.user_agent, "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0") self.assertEqual(event.user_agent, "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0")
@@ -211,7 +211,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
"sg_event_id": "OTdlOGUzYjctYjc5Zi00OWE4LWE4YWUtNjIxNjk2ZTJlNGVi", "sg_event_id": "OTdlOGUzYjctYjc5Zi00OWE4LWE4YWUtNjIxNjk2ZTJlNGVi",
"sg_message_id": "_fjPjuJfRW-IPs5SuvYotg.filter0590p1mdw1.2098.57168CFC4B.0", "sg_message_id": "_fjPjuJfRW-IPs5SuvYotg.filter0590p1mdw1.2098.57168CFC4B.0",
"useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36", "useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36",
"smtp-id": "<20160421010427.2847.6797@example.com>", "anymail_id": "75de5af9-a090-4325-87f9-8c599ad66f60",
"event": "click", "event": "click",
"url_offset": {"index": 0, "type": "html"}, "url_offset": {"index": 0, "type": "html"},
"email": "recipient@example.com", "email": "recipient@example.com",
@@ -227,7 +227,7 @@ class SendGridDeliveryTestCase(WebhookTestCase):
self.assertIsInstance(event, AnymailTrackingEvent) self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "clicked") self.assertEqual(event.event_type, "clicked")
self.assertEqual(event.esp_event, raw_events[0]) self.assertEqual(event.esp_event, raw_events[0])
self.assertEqual(event.message_id, "<20160421010427.2847.6797@example.com>") self.assertEqual(event.message_id, "75de5af9-a090-4325-87f9-8c599ad66f60")
self.assertEqual(event.event_id, "OTdlOGUzYjctYjc5Zi00OWE4LWE4YWUtNjIxNjk2ZTJlNGVi") self.assertEqual(event.event_id, "OTdlOGUzYjctYjc5Zi00OWE4LWE4YWUtNjIxNjk2ZTJlNGVi")
self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.user_agent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36") self.assertEqual(event.user_agent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36")

View File

@@ -4,6 +4,7 @@ import logging
import os import os
import re import re
import sys import sys
import uuid
import warnings import warnings
from base64 import b64decode from base64 import b64decode
from contextlib import contextmanager from contextlib import contextmanager
@@ -165,6 +166,14 @@ class AnymailTestMixin:
second = rfc822_unfold(second) second = rfc822_unfold(second)
self.assertEqual(first, second, msg) self.assertEqual(first, second, msg)
def assertUUIDIsValid(self, uuid_str, version=4):
"""Assert the uuid_str evaluates to a valid UUID"""
try:
uuid.UUID(uuid_str, version=version)
except (ValueError, AttributeError, TypeError):
return False
return True
# Backported from Python 3.4 # Backported from Python 3.4
class _AssertLogsContext(object): class _AssertLogsContext(object):