8

I'm trying to automate the creation of drafts via the Gmail API, and I want these drafts to be responses to existing emails. To do this, I believe I need to set the "threadId" header (Gmail specific), the "References" header, and the "In-Reply-To" header. Additionally, for Gmail to consider the message to be a reply, the "Subject" header must match the original email.

I'm hardcoding all of these headers into a MIMEText object, and then base-64 encoding (urlsafe) the message as a string and having the Gmail API deliver it. However, the "threadId", "In-Reply-To", and "References" headers don't appear to ever make it in the email that's sent, as they don't exist in the MIME shown when clicking "Show original" in the Gmail UI.

new = MIMEText("reply body text")
new["In-Reply-To"] = "[Message-ID of email to reply to]" #looks like <..@mail.gmail.com>
new["References"] = "[Message-ID of email to reply to]" #looks like <..@mail.gmail.com>
new["threadId"] = "[threadId of message to reply to]" #looks like 14ec476abbce3421
new["Subject"] = "Testsend2"
new["To"] = "[Email to send to]"
new["From"] = "[Email to send from]"

messageToDraft = {'raw': base64.urlsafe_b64encode(new.as_string())}
message = {'message': messageToDraft}
draft = service.users().drafts().create(userId="me", body=message).execute()
monstermac77
  • 256
  • 3
  • 24
  • The `threadId` should not be sent as part of the `raw` key, for it to work it has to be separate.So `message` should contain {`raw`: my_raw, `threadId`: my_thread_id} – gdvalderrama Sep 19 '17 at 11:27

2 Answers2

13

Actually, it's a lot simpler than that! If you just supply the correct Subject in the headers, and the correct threadId in the body, Google will calculate all the references for you.

new = MIMEText("This is the placeholder draft message text.")
new["Subject"] = "Example Mail"
new["To"] = "emtholin@gmail.com"
new["From"] = "emtholin@gmail.com"

raw = base64.urlsafe_b64encode(new.as_string())
message = {'message': {'raw': raw, 'threadId': "14ec598be7f25362"}}
draft = service.users().drafts().create(userId="me", body=message).execute()

This results in a draft, ready to be sent in the correct thread:

enter image description here

Then, I send the mail. As you can see, the references are calculated for you:

MIME-Version: 1.0
Received: by 10.28.130.132 with HTTP; Sat, 25 Jul 2015 07:54:12 -0700 (PDT)
In-Reply-To: <CADsZLRz5jWF5h=6Cs1F45QQOiFuqNGmMeb6St5e-tOj3stCNiA@mail.gmail.com>
References: <CADsZLRwmDZ_L5_zWqE8qOgoKuvRiRTWUopqssn4+XYGM_SKrfg@mail.gmail.com>
    <CADsZLRz5jWF5h=6Cs1F45QQOiFuqNGmMeb6St5e-tOj3stCNiA@mail.gmail.com>
Date: Sat, 25 Jul 2015 16:54:12 +0200
Delivered-To: emtholin@gmail.com
Message-ID: <CADsZLRxuyFhuGNPwjRrfFVQ0_2MxO=_jstjmsBGmAiwMEvfWSg@mail.gmail.com>
Subject: Example Mail
From: Emil Tholin <emtholin@gmail.com>
To: Emil Tholin <emtholin@gmail.com>
Content-Type: text/plain; charset=UTF-8

This is the placeholder draft message text.
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 1
    Very good solution, thank you. I later realized that something similar to this was possible, although I prefer your solution: Gmail will calculate and append the necessary headers if you generate a draft with the same recipient and with "Re:" prepended to the subject. – monstermac77 Jul 26 '15 at 23:33
  • 1
    @monstermac77 No problem! :) Cool! Good to know. – Tholle Jul 27 '15 at 07:15
  • @Tholle Any idea how to do the same thing within Google Apps Script? It seems to ignore the threadID in:"message": { "raw": rawEncoded, "threadId": threadId} – Layla Jun 10 '16 at 00:06
  • @Layla I have never used Apps Script, sadly. Maybe this documented [reply function](https://developers.google.com/apps-script/reference/gmail/gmail-thread#replybody) could give some clues? – Tholle Jun 10 '16 at 06:55
  • This creates a draft reply but it differs from when I create one manually. When I do it manually in Gmail: click reply then start typing a message then navigate away and return back to the email you can see the email draft still there at the bottom of the original email. This doesn't happen when I use the API. – K-Dawg Sep 14 '17 at 13:18
  • @Tholle this created draft doesnt attach the conversation and when I hit send button, it sends as a new email not a conversational email. Any ideas, how to include previous email too ? – user96564 Aug 17 '19 at 17:17
  • For anyone else who might be confused, the `message_id` that you have to include when creating the reply email is NOT `message['id']`, but instead (and quite confusingly IMHO) it's `header['value']`, where `header` is the unique header in `message['headers']` whose `header['name']` is 'Message-ID'. And the two are very much not the same. Oops – Juan Carlos Ortiz Mar 29 '22 at 16:25
1

if you want to not only create the draft but also additionally send it then extension of above code (one additional line after draft =...create().execute():

    draft = service.users().drafts().create(userId="me", body= message).execute()
    message = service.users().drafts().send(userId='me', body={'id': draft['id']}).execute()
Ani
  • 11
  • 2