Random 404 client errors while using the Microsoft Graph API for fetching emails

2023-02-24

A few months ago, Microsoft forced everyone who was programmatically fetching emails from Microsoft’s hosted email to switch from the standard SMTP protocol to their APIs instead.

Thankfully, there is the python o365 lib which does the heavy lifting. Even django-mailbox supports it now :-)

Anyway, it was working pretty well for a while. But then the o365 lib started throwing some random 404 client errors which looked like this:

HTTPError: 404 Client Error: Not Found for url: https://graph.microsoft.com/v1.0/users/example@example.com/messages/some-long-base64-encoded-string-here==/$value | Error Message: The specified object was not found in the store.

Due to this error some imported attachments were empty.

It turns out Microsoft’s Office 365 Advanced Threat Protection (ATP) was the culprit. When ATP is ON you can see the emails immediately after they have been received, you can also see how many attachments each email has and even see the attachment file names. But as long as the ATP scan is in progress you cannot download the attachments. The web interface shows an “ATP Scan in progress” message, but the API client sees only that the attachments are empty.

As a quick fix you can wait e.g. 10 minutes before fetching the newly arrived emails. Using the o365 library it looks like this:

import datetime as dt
from O365 import Account
credentials = ('client_id', 'client_secret')
account = Account(credentials, auth_flow_type='credentials', tenant_id='tenant_id')
if account.authenticate():
   print('Authenticated!')

mailbox = account.mailbox(resource="example@example.com")
inbox = mailbox.inbox_folder()
# wait 10 minutes before fetching emails, due to "ATP Scan in progress"
# some attachments are empty when fetched while the scan is still running
query = inbox.q('received_date_time').less(dt.datetime.now() - dt.timedelta(minutes=10))
for message in inbox.get_messages(query=query):
     print(message)

Alternatively, you could try to read the deviceThreatProtectionLevel status to determine if an email with attachments is ready for a download or not, but at the time of this writing I could not see a way to access this information from the python client.

This is not THE solution for every graphql HTTPError: 404 Client Error error, but if you get empty attachments give it a try, it might be due to ATP.

Articlesapioutlookmicrosoftexchange

How to migrate unsalted SHA1 password hashes to PBKDF2 in django