Skip to content

from_email_message crashes on non-MIMEBase email.message.Message attachments #305

Description

@jefftriplett

RelayEmailData.from_email_message raises AttributeError: 'int' object has no attribute 'lower' when an attachment is a stdlib email.message.Message that is not a MIMEBase subclass.

This is currently the single largest error source in our Sentry org — 465,554 occurrences (LEASE-BACKEND-73, Seer actionability super_high), with a second instance in a sibling task (LEASE-BACKEND-72).

Cause

src/email_relay/email.py branches on isinstance(attachment, MIMEBase):

for attachment in email_message.attachments:
    if isinstance(attachment, MIMEBase):
        ...
    else:
        if isinstance(attachment[1], bytes):   # <-- assumes a tuple

The else branch assumes a (filename, content, mimetype) tuple and subscripts it. MIMEBase is a subclass of email.message.Message, but the reverse is not true — a plain email.message.Message (or email.message.EmailMessage) fails the isinstance check, falls into else, and attachment[1] invokes Message.__getitem__(1), which does name.lower() on the int 1:

  File "email_relay/email.py", line 86, in from_email_message
    if isinstance(attachment[1], bytes):
  File "email/message.py", line 429, in __getitem__
    return self.get(name)
  File "email/message.py", line 510, in get
    name = name.lower()
AttributeError: 'int' object has no attribute 'lower'

Because Message defines __getitem__, this fails deep inside stdlib with a confusing error instead of a clear type error at the boundary.

Suggested fix

Widen the isinstance check to the base class, so every Message subclass takes the message branch:

from email.message import Message

if isinstance(attachment, Message):
    ...

MIMEBase instances still match, and get_payload/get_filename/get_content_type are all defined on Message, so the existing branch body should work unchanged. Worth considering an explicit else guard that raises a clear TypeError when the attachment is neither a Message nor a 3-tuple, so a future mismatch fails legibly.

Reproducing

Attach a plain email.message.Message (not a MIMEBase subclass) to a Django EmailMessage and send it through the relay backend.

Notes

Observed on CPython 3.12.13, environment: production, via django_q workers. Reported from the Sentry issue above; the calling code is in westerveltco/lease (lease/clubs/tasks.py:268), but the defect is here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions