1

I’m building a Python script using the Gmail API to read model application emails, extract data, and download photo attachments. Everything works except the attachments. the script keeps saying: Attachments: None

**My Question:**Why is the Gmail API detecting filenames but returning no attachment data, and how do I correctly extract and save image attachments from Gmail messages?

Please see below for information I tried to include to best explain the situation. I'm very new to this so just learning and trying to get some help figuring out what I might be missing. Thanks in advance!

My environment: Python 3.10, Running on Replit, Gmail API v1, using OAuth 2.0 client ID + secret

In the CSV I generate, the attachment column shows the image filenames correctly. So Gmail clearly sees that there are attachments, but nothing is actually being downloaded, and no files are saved in the attachments folder.

What i'm trying to do:

  • Have the script loop through unread emails

  • save each attachment (jpg/png) into the attachments folder

  • write the filename into a csv with client name, email, etc.

what is happening:

  • csv updates correctly

  • the script logs "attachments: none" for every email

  • the attachments folder is empty

  • no errors shown

Relevant code:

for msg in messages:
    msg_id = msg["id"]
    try:
        message = service.users().messages().get(userId="me", id=msg_id).execute()

        # Extract parts
        payload = message.get("payload", {})
        parts = payload.get("parts", [])

        attachments = []

        for part in parts:
            if part.get("filename"):
                attach_id = part["body"].get("attachmentId")
                if attach_id:
                    attachment = service.users().messages().attachments().get(
                        userId="me", messageId=msg_id, id=attach_id
                    ).execute()

                    data = base64.urlsafe_b64decode(attachment["data"])
                    
                    filepath = os.path.join("attachments", part["filename"])
                    with open(filepath, "wb") as f:
                        f.write(data)

                    attachments.append(part["filename"])
    except Exception as e:
        print(f"Error processing {msg_id}: {e}")

Logging:

Found 8 emails.

- Processed 19a6e50508c6d5b2, saved to CSV. Name: Shannah Copeland, Attachments: None

- Processed 19a5edcd93003ccf, saved to CSV. Name: Taylor Newport, Attachments: None

What I’ve tried

  • -Verified that Gmail API scopes include:
    https://www.googleapis.com/auth/gmail.readonly
    https://www.googleapis.com/auth/gmail.modify
    -Ensured attachments folder exists before writing
    -Printed part["filename"] — it shows up correctly
    -Printed MIME type — sometimes “image/jpeg”, sometimes nested
    -Tried decoding with both base64.urlsafe_b64decode and b64decode
    -Confirmed Gmail messages do have attachments manually in Gmail
    -Verified OAuth credentials and token.json are valid

Maybe the attachments are nested in alternative structures? But when I click them in my own email they download immediately.

I’ve been stuck on this for days and can’t get the images to download even though Gmail clearly sees them.

New contributor
Tess is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

1 Answer 1

1

Your script only looks at the first level of parts, so attachments nested deeper get skipped. By recursively traversing all parts and checking both `attachmentId` and inline `data`, you should be able to download the images correctly.

To fix it, you need to recursively walk through all parts of the message payload and handle both cases (`attachmentId` vs inline `data`).

Try this:

def save_attachments(service, msg_id, payload, save_dir="attachments"):
    attachments = []

    def walk_parts(parts):
        for part in parts:
            filename = part.get("filename")
            mime_type = part.get("mimeType")
            body = part.get("body", {})
            
            if filename and (body.get("attachmentId") or body.get("data")):
                if body.get("attachmentId"):
                    attach_id = body["attachmentId"]
                    attachment = service.users().messages().attachments().get(
                        userId="me", messageId=msg_id, id=attach_id
                    ).execute()
                    data = attachment.get("data")
                else:
                    # Inline base64 data
                    data = body.get("data")

                if data:
                    file_data = base64.urlsafe_b64decode(data)
                    filepath = os.path.join(save_dir, filename)
                    with open(filepath, "wb") as f:
                        f.write(file_data)
                    attachments.append(filename)

            # Recurse into nested parts
            if "parts" in part:
                walk_parts(part["parts"])

    walk_parts(payload.get("parts", []))
    return attachments

(PS: I am new to stackoverflow, so all comments are appreciated)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.