15

Upon using the Gmail API in Javascript to send a message with an HTML body and a ~100KB PDF attachment, the attachment correctly appears attached to the message in the sender's Gmail Sent folder, but does not appear on the message for the recipient.

The API call is a POST to:

https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media

The request body sent to the API is:

{
  "headers": {
    "Authorization": "Bearer authToken-removedForThisPost"
  },
  "method": "POST",
  "contentType": "message/rfc822",
  "contentLength": 134044,
  "payload": "exampleBelow",
  "muteHttpExceptions": true
}

This is what the payload looks like:

MIME-Version: 1.0
To: =?utf-8?B?TWlrZSBD?=<recipient@test.com>
CC: =?utf-8?B?TWlrZSBD?=<secondrecipient@gmail.com>
BCC: =?utf-8?B??=<bccrecipient@test.com>
From: =?utf-8?B?TWlrZSBxWXsd2lr?=<sender@test.com>
Subject: =?utf-8?B?subjectLine-removedForThisPost?=
Content-Type: multipart/alternative; boundary=__boundary__

--__boundary__
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64

base64EncodedStringHere-removedForThisPost

--__boundary__
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64

base64EncodedStringHere-removedForThisPost

--__boundary__
Content-Type: application/pdf; name="File Name.pdf"
Content-Disposition: attachment; filename="File Name.pdf"
Content-Transfer-Encoding: base64

base64EncodedStringHere-removedForThisPost

--__boundary__--

Note: The Gmail API Uploading Attachments documentation states that when uploading a simple attachment (under 5MB) Content-Length is required. I made it so that my code produces an integer value of the total number of bytes of the PDF attachment. However, I noticed that Content-Length is not included in the payload.

I tried changing the multipart/alternative Content-Type for the message to multipart/mixed - this made it so that the PDF attachment IS correctly attached to the recipient's message, but the HTML body of the message is rendered as plain text (the HTML tags are shown) and there is an additional attachment called noname.html which includes the HTML content rendered as HTML.

I need to make it so that the email in the recipient's message has both an HTML-rendered body AND the PDF attachment.

Update: I uploaded examples of the raw email messages here. The sent message is on the left, and the received message is on the right.

Employee
  • 1,732
  • 2
  • 24
  • 44
  • Have you tried quoting your boundary (`boundary="__boundary__"`) and using the final boudnary (`--__boundary__--`)? Try something [**like this**](http://pastebin.com/SRZjEGfT) and see if it works. – Tholle Feb 14 '17 at 12:50
  • Just realized that my payload DID include the final boundary of `--__boundary__--`, but it was cut off when I pasted it here because the console.log message was truncated due to the super long attachment base64 string. As for the double quotes - I added them to the first `Content-Type:` line but it didn't change the behavior at all - it works the same with or without them. – Employee Feb 14 '17 at 23:33
  • Just to check, is this message been received with the same attachment when is sent from the UI? Also, is this happening with all the recipients ? or just to an specif domain/user? – jds1993 Aug 29 '19 at 10:45
  • Yes, when sending a message with the attachment in the Gmail UI, it is correctly received by the recipient. I've also tested with multiple different attachments to rule out the possibility of an issue with the file. And I've tested with several different recipients in different domains, and the attachment is missing for all recipients. – Employee Aug 29 '19 at 17:07

2 Answers2

5

Just replace:

Content-Type: multipart/alternative; boundary=__boundary__

for:

Content-Type: multipart/mixed; boundary=__boundary__

This is my full function written in JS

function createMimeMessage_(msg) {

var nl = "\n"; var boundary = "ctrlq_dot_org";

var mimeBody = [

"MIME-Version: 1.0",
"To: "      + msg.to.email,//+ encode_(msg.to.name) + "<" + msg.to.email + ">",
"Cc: "      + msg.cc.email,
"Bcc: "      + msg.bcc.email,
"From: "    + msg.from.email,//+ encode_(msg.from.name) + "<" + msg.from.email + ">",
"Subject: " + encode_(msg.subject), // takes care of accented characters
"In-Reply-To: " + (msg.reply_to || ""),
"References: " + (msg.reply_to || ""),

"Content-Type: multipart/mixed; boundary=" + boundary + nl,
"--" + boundary,

// "Content-Type: text/plain; charset=UTF-8",
// "Content-Transfer-Encoding: 7bit",
// "Content-Disposition: inline" + nl,
// msg.body.text + nl,
// "--" + boundary,

"Content-Type: text/html; charset=UTF-8",
"Content-Transfer-Encoding: base64" + nl,
new Buffer(msg.body.text).toString('base64') + nl,

];

for (var i = 0; i < msg.files.length; i++) {

var attachment = [
  "--" + boundary,
  "Content-Type: " + msg.files[i].mimeType + '; name="' + msg.files[i].fileName + '"',
  'Content-Disposition: attachment; filename="' + msg.files[i].fileName + '"',
  "Content-Transfer-Encoding: base64" + nl,
  msg.files[i].bytes
];

mimeBody.push(attachment.join(nl));

}

mimeBody.push("--" + boundary + "--"); //console.log(mimeBody);

return mimeBody.join(nl);

}

Tiger developer
  • 225
  • 3
  • 10
  • I mentioned in my original question that I'd tried that already: this made it so that the PDF attachment IS correctly attached to the recipient's message, but the HTML body of the message is rendered as plain text (the HTML tags are shown) and there is an additional attachment called noname.html which includes the HTML content rendered as HTML. – Employee May 31 '17 at 18:20
  • Hey, thanks for including the whole function. However, it looks like you just copied most, if not all of that, from Amit Agarwal's page [here](https://ctrlq.org/code/20133-create-gmail-drafts-attachments), which is where I originally got most of my code from, so it matches almost exactly. – Employee Jun 06 '17 at 21:12
  • Exactly, I copied from that site. But I made some changes to get it work for what I need. – Tiger developer Jun 07 '17 at 14:22
0

There are two parts to your question:

  1. How do I get the attachment to go through to the recipient?
  2. How do I include both attachment and the plain text alternative to the HTML?

This was partially answered by Tiger developer (multipart/alternative to multipart/mixed). The problem, as you noted, is that simply doing this will prevent you from having the alternative plain text. This is because you're removing the multipart/alternative, whose specific role is to provide that alternative.

What you need to do is create a second boundary and then group the plain text & HTML parts together. Take a look at this example, also sourced from CTRLQ, and note the altBoundary that I included.

function createMimeMessage_(msg) {

  var nl = "\n";
  var boundary = "__ctrlq_dot_org__";
  var altBoundary = "__alt_ctrlq_dot_org__";

  var mimeBody = [

    "MIME-Version: 1.0",
    "To: "      + encode_(msg.to.name) + "<" + msg.to.email + ">",
    "From: "    + encode_(msg.from.name) + "<" + msg.from.email + ">",
    "Subject: " + encode_(msg.subject), // takes care of accented characters

    "Content-Type: multipart/mixed; boundary=" + boundary + nl,
    "--" + boundary,

    "Content-Type: multipart/alternative; boundary=" + altBoundary + nl,
    "--" + altBoundary,

    "Content-Type: text/plain; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.text, Utilities.Charset.UTF_8) + nl,
    "--" + altBoundary,

    "Content-Type: text/html; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.html, Utilities.Charset.UTF_8) + nl,

    "--" + altBoundary + "--"

  ];

  for (var i = 0; i < msg.files.length; i++) {

    var attachment = [
      "--" + boundary,
      "Content-Type: " + msg.files[i].mimeType + '; name="' + msg.files[i].fileName + '"',
      'Content-Disposition: attachment; filename="' + msg.files[i].fileName + '"',
      "Content-Transfer-Encoding: base64" + nl,
      msg.files[i].bytes
    ];

    mimeBody.push(attachment.join(nl));

  }

  mimeBody.push("--" + boundary + "--");

  return mimeBody.join(nl);

}
Diego
  • 8,070
  • 2
  • 13
  • 26