I'm inserting emails with the API into my mailbox. Attachments work and do show up in the email itself. Usually in the inbox/message list view, there is an attachment paperclip symbol at the right end to indicate the message has an attachment (https://i.sstatic.net/TtiyC.png). But this doesn't seem to be working. My code to generate the email and insert it is below. Is this a limitation of the insert API or I'm not generating the email with attachment correctly?
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from base64 import urlsafe_b64encode
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email import encoders
import mimetypes
import os
from apiclient import errors
from urllib.error import HTTPError
import time
import datetime
import re
import random
SCOPES = ['https://www.googleapis.com/auth/gmail.insert']
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
message = MIMEMultipart('alternative')
part1 = MIMEText('just text', 'plain')
part2 = MIMEText('<b>html text<br><br>html text</b>', 'html')
message.attach(part1)
message.attach(part2)
filename = 'some.attachment'
part = MIMEApplication(open('/var/www/html/attachments/' + filename, 'rb').read())
part.add_header('Content-Disposition', f"attachment; filename=\"{filename}\"; size={os.path.getsize('/var/www/html/attachments/' + filename)}")
part.add_header('Content-Description', f"{filename}")
message.attach(part)
message['to'] = '"Some Body" <somebody@gmail.com>'
message['from'] = 'Some Other <from@email.com>'
message['subject'] = 'test email'
message['Content-Type'] = 'text/html; charset="utf-8"'
message['Content-Transfer-Encoding'] = 'base64'
encoded_message = urlsafe_b64encode(message.as_bytes())
temp = {'raw': encoded_message.decode(), 'labelIds': ['Some_Label', 'UNREAD']}
try:
message1 = service.users().messages().insert(userId='me', body=temp, internalDateSource='dateHeader').execute()
print(f"{message1['id']} / {message1['threadId']}/ {message1}")
except HTTPError as err:
print(err)
Is this a limitation of the insert API or I'm not generating the email with attachment correctly?
It appears that this is indeed a limitation of the Gmail API.
I did a bit of testing with the API and the Gmail web interface and this behaviour is reproducable in the Gmail UI if the attachment is inserted into the body of the message rather than via the upload attachment button.
This is what is done when the methods messages: insert
and messages: send
of the Gmail API are used - the attachment is placed in the body of the message rather than attached externally so this is reflected in the UI.
It might be worth noting that the emails are still retrievable in the Gmail UI when using the search term has:attachment
, regardless of how the email is placed there (API insert, API send, or standard sending from an email client.
You can however let Google know that replicating the way attachements are inserted to messages in the UI is a feature that is important for the Gmail API. Google's Issue Tracker is a place for developers to report issues and make feature requests for their development services.