3

I'm struggling to find a good example of the full set of requests necessary to send an email through the Gmail API containing an attachment larger than 10mb.

I've seen https://developers.google.com/gmail/api/v1/reference/users/messages/send and https://developers.google.com/gmail/api/guides/uploads#resumable, but there's nothing that ties it all together.

We're using the ruby client, but we're unable to complete this flow. With the following code, we get the following error trying to make the second request: Google::APIClient::ClientError: Recipient address required

The full body of the response is the following:

{"error"=>{"errors"=>[{"domain"=>"global", "reason"=>"invalidArgument", "message"=>"Recipient address required"}], "code"=>400, "message"=>"Recipient address required"}}

Here's the code used to generate the request:

raw = Base64.urlsafe_encode64 message_string
result1 = api_client.execute!(
  :api_method => gmail_api.users.messages.to_h['gmail.users.messages.send'],
  :parameters => {
    :uploadType => 'resumable',
    :userId => 'me'
  },
  :headers => {
    'Content-Type' => 'application/json',
    'X-Upload-Content-Type' => 'message/rfc822',
    'X-Upload-Content-Length' => raw.bytesize.to_s
  }
)

upload_id = result1.headers['x-guploader-uploadid']

result2 = api_client.execute!(
  :api_method => gmail_api.users.messages.to_h['upload.gmail.users.messages.send'],
  :body_object => {
    :raw => raw
  },
  :parameters => {
    :uploadType => 'resumable',
    :upload_id => upload_id,
    :userId => 'me'
  },
  :headers => {
    'Content-Type' => message.content_type,
    'Content-Length' => raw.bytesize.to_s
  }
)
jwg2s
  • 806
  • 1
  • 12
  • 25
  • Have you tried it with an actual email address instead of `:userId => 'me'`? From their docs: "The special value 'me' can be used to indicate the authenticated user." I'm wondering if your user is not authenticated (I can't tell because I assume that happens as part of creating `api_client`) – supremebeing7 Aug 06 '18 at 23:02
  • This may or may not be helpful as well: https://stackoverflow.com/a/43684252/3477163 – supremebeing7 Aug 06 '18 at 23:03
  • 1
    Not Ruby, but [this answer might give some inspiration](https://stackoverflow.com/questions/24908700/mail-attachment-wrong-media-type-gmail-api/24957873#24957873). – Tholle Aug 06 '18 at 23:22
  • 1
    @Tholle - thanks so much! The issue was that when using multipart uploadType, the body of the request should *not* be base64 encoded, which I don't think is mentioned anywhere in the docs sadly. – jwg2s Aug 07 '18 at 13:52

2 Answers2

2

So the issue (thank you to @tholle) was that when sending attachments greater than 5mb and less than 35mb (but also works on messages without attachments), you do NOT base64 encode the body of the request, and use multipart as the uploadType. Unfortunately the docs don't mention this at all, and the error messages don't indicate that either.

Here's a working example that was able to send a 20mb attachment. Hopefully this will help anyone else who has wasted countless hours trying to figure this one out!

  result = api_client.execute!(
    :api_method => gmail_api.users.messages.to_h['gmail.users.messages.send'],
    :body => rfc822_message_string,
    :parameters => {
      :uploadType => 'multipart',
      :userId => 'me'
    },
    :headers => {
      'Content-Type' => 'message/rfc822',
    }
  )
jwg2s
  • 806
  • 1
  • 12
  • 25
0

I am working on a JavaScript client and I finally found a way to send email using resumable method. (Thank you @Tholle and @jwg2s). Although this is in JavaScript client it should work about the same in other client too.

This is what I did:


// MIME Mail Message data. Copied form above @Tholle 's message.
let mail = [
  'Content-Type: multipart/mixed; boundary="foo_bar_baz"\r\n',
  "MIME-Version: 1.0\r\n",
  "to: to@gmail.com\r\n",
  "from: from@gmail.com\r\n",
  "subject: i am subject\r\n\r\n",

  "--foo_bar_baz\r\n",
  'Content-Type: text/plain; charset="UTF-8"\r\n',
  "MIME-Version: 1.0\r\n",
  "Content-Transfer-Encoding: 7bit\r\n\r\n",

  "The actual message text goes here\r\n",
  "--foo_bar_baz\r\n",
  "Content-Type: application/json; name=package.json\r\n",
  "Content-Transfer-Encoding: base64\r\n",
  "Content-Disposition: attachment; filename=package.json\r\n\r\n",

  "<base64 file data. data according to content type>",
  "\r\n",
  "--foo_bar_baz--",
].join("");

// get resumable upload link.
let resumableURL = "";
gapi.client
    .request({
      path: "/upload/gmail/v1/users/me/messages/send?uploadType=resumable",
      headers: {
        "X-Upload-Content-Type": "message/rfc822",
      },
      method: "post",
    })
    .then(
      (res) => {
        resumableURL = res.headers.location;
        console.log(res);
      },
      (err) => console.log(err)
    );

// send email
gapi.client
    .request({
      path: resumableURL,
      headers: {
        "Content-Type": "message/rfc822",
      },
      method: "post",
      body: mail,
    })
    .then(
      (res) => {
        console.log(res);
      },
      (err) => console.log(err)
    );

To convert gapi.client.request to Fetch API call you just need to add Authorization: Bearer <access_token> to header field. I have tried using Fetch API but response were blocked due to cors error so api client like Postman should be used.

To do more with resumable upload method check documentation: Upload Attachment

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Bipin Maharjan
  • 423
  • 6
  • 13