Reputation: 116
I created two Gmail accounts and I'm trying to create an e-mail thread between them with the Python Gmail API.
I can send e-mails without any issue, but when it comes to replying to each other and creating a thread, it is simply not working : the new message is successfully displaying as the answer of the received email for the sender, but it is appearing as a new message - without a linked thread - for the receiver.
This problem was described here in 2019 : https://stackoverflow.com/a/63186609/21966625
However, the Gmail APIs changed a lot since this article and I didn't find how to use these advices with today's API.
I tried to carefully respect the instructions of the docs by defining the message's parameters References
and In-Reply-To
as the received message's id when replying.
Indeed, I retrieve the email :
received_email= service.users().messages().get(userId='me', id=label['id']).execute()
I get a dict looking like:
{'id': '189462395f418017', 'threadId': '189462395f418017', 'labelIds': ['UNREAD','INBOX'], 'snippet': 'xxx'....}
Hence, when I'm building my e-mail, the following method should work :
message_id=received_email['id']
message = EmailMessage()
message.set_content('')
message['To'] = '[email protected]'
message['From'] = '[email protected]'
message['References'] = message_id
message['In-Reply-To'] = message_id
message['Subject'] = 'Automated draft'
In the same way, I defined the threadId
as the id of the message I wanted to reply to.
create_message = {'raw': encoded_message,
'threadId': message_id
}
send_message = (service.users().messages().send(userId="me", body=create_message).execute())
Thanks to this part of the code, the answers are correctly displayed (for the sender of the answer) as explained above, but it appears as a new message - unlinked to a thread - for the receiver.
Upvotes: 3
Views: 2395
Reputation: 116
Actually I found why my method did not work ; even if the dict mention a kind of message id :
email = {'id': '189462395f418017', 'threadId': '189462395f418017', 'labelIds': ['UNREAD','INBOX'], 'snippet': 'xxx'....}
I thought the messageID
could be taken just by call email['id']
.
The real messageID
is somewhere in the ['payload']['headers']
dictionnary ; one could find it by a loop like :
for p in email['payload']['headers']:
if p["name"] == "Message-Id":
message_id = p['value']
This way we have the true messageID
of the email, and the threads are successfully created.
Upvotes: 5
Reputation: 1610
You are on the right track using the message's parameters References
and In-Reply-To
on your reply body but the issue lies on the messageId
and threadId
that you have obtained from your previous request.
You can alternatively use the code below that ensures you obtain the threadId
of the thread that you want.
You just need to specify the subject
of the thread that you want to reply to. The code below behaves as follows:
users.threads.list
subject
of the thread that you want to replyusers.threads.get
users.messages.send
"""Authorization and Authentication of Account to Access GMAIL API """
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from email.message import EmailMessage
import base64
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://mail.google.com/','https://www.googleapis.com/auth/gmail.modify','https://www.googleapis.com/auth/gmail.modify','https://www.googleapis.com/auth/gmail.modify']
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# 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.json', 'w') as token:
token.write(creds.to_json())
"""Authorization and Authentication of Account to Access GMAIL API """
# Set up the Gmail API client
service = build('gmail', 'v1', credentials=creds)
# Specify the subject to match
subject = 'Sample email thread'
# Retrieve the list of threads
threads = service.users().threads().list(userId='me').execute().get('threads', [])
# Find the thread with the matching subject
matching_thread = None
for thread in threads:
thread_id = thread['id']
thread_details = service.users().threads().get(userId='me', id=thread_id).execute()
message = thread_details['messages'][0] # Get the first message in the thread
message_subject = next((header['value'] for header in message['payload']['headers'] if header['name'] == 'Subject'), None)
if message_subject == subject:
matching_thread = thread
break
if matching_thread:
thread_id = matching_thread['id']
else:
print("No matching thread found.")
# Retrieve the details of the thread
thread = service.users().threads().get(userId='me', id=thread_id).execute()
messages = thread['messages'][0]['payload']['headers']
# Retrieve the metadata of the thread
for (k) in messages:
if k['name'] == 'To':
recipient = k['value']
if k['name'] == 'Subject':
email_subject = k['value']
if k['name'] == 'From':
sender = k['value']
if k['name'] == 'Message-ID':
message_id = k['value']
# Constructing the reply message
message = EmailMessage()
message.set_content('This is a sample reply')
message['To'] = recipient
message['From'] = sender
message['Subject'] = email_subject
message['References '] = message_id
message['In-Reply-To '] = message_id
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
create_message = {'raw': encoded_message,
'threadId': thread_id}
# Sending the reply message to the thread
send_message = (service.users().messages().send(userId="me", body=create_message).execute())
Note: My code on authorization and authentication part may differ from yours
Upvotes: 1
Reputation: 63
You'll need to send the reply message as part of a thread. You'll need to identify the thread ID from the original message, not the message ID, and add that as part of the new message's message object...
create_message = {
'raw': encoded_message,
'threadId': threadId
}
https://developers.google.com/gmail/api/guides/threads https://developers.google.com/gmail/api/reference/rest/v1/users.messages#Message
You may also need to include the headers "References" and "In-Reply-To" with the message ID of the last message.
Upvotes: 2