Home > Mobile >  Creating an Emoji-Compatible Gmail draft with Advanced Gmail Service in Google Apps Script
Creating an Emoji-Compatible Gmail draft with Advanced Gmail Service in Google Apps Script

Time:10-12

I'm trying to enter image description here

The script that retrives this draft message:

const getMessage = () => {
  const id = 'r-8326849559354985208';
  const msg = Gmail.Users.Drafts.get('[email protected]', id);
  console.log(JSON.stringify(msg, null, 2));
}

The output of the script:

{
  "message": {
    "internalDate": "1633701716000",
    "snippet": "Draft body",
    "labelIds": [
      "DRAFT"
    ],
    "historyId": "954861",
    "sizeEstimate": 534,
    "payload": {
      "filename": "",
      "parts": [
        {
          "partId": "0",
          "headers": [
            {
              "value": "text/plain; charset=\"UTF-8\"",
              "name": "Content-Type"
            }
          ],
          "filename": "",
          "body": {
            "data": [
              68,
              114,
              97,
              102,
              116,
              32,
              98,
              111,
              100,
              121,
              13,
              10
            ],
            "size": 12
          },
          "mimeType": "text/plain"
        },
        {
          "headers": [
            {
              "value": "text/html; charset=\"UTF-8\"",
              "name": "Content-Type"
            }
          ],
          "partId": "1",
          "body": {
            "size": 33,
            "data": [
              60,
              100,
              105,
              118,
              32,
              100,
              105,
              114,
              61,
              34,
              108,
              116,
              114,
              34,
              62,
              68,
              114,
              97,
              102,
              116,
              32,
              98,
              111,
              100,
              121,
              60,
              47,
              100,
              105,
              118,
              62,
              13,
              10
            ]
          },
          "mimeType": "text/html",
          "filename": ""
        }
      ],
      "body": {
        "size": 0
      },
      "headers": [
        {
          "value": "1.0",
          "name": "MIME-Version"
        },
        {
          "value": "Fri, 8 Oct 2021 16:01:56  0200",
          "name": "Date"
        },
        {
          "value": "<CADVhnimBt3Jdod1wBgGUgB_75yrsoJMwM68mtYKmX6cN39=CNQ@mail.gmail.com>",
          "name": "Message-ID"
        },
        {
          "name": "Subject",
          "value": "Draft subject"
        },
        {
          "name": "From",
          "value": "\"KOSTYUK, Dmitry\" <[email protected]>"
        },
        {
          "value": "multipart/alternative; boundary=\"00000000000088918105cdd7d2e1\"",
          "name": "Content-Type"
        }
      ],
      "mimeType": "multipart/alternative",
      "partId": ""
    },
    "id": "17c6035e45454be8",
    "threadId": "17c6035c50e83b2f"
  },
  "id": "r-8326849559354985208"
}

CodePudding user response:

After some research and learning the RFC2822 MIMEText syntax, I have the definite answer to the problem. I will answer in three parts:

  1. What Didn't Work
  2. Solution One: the Hard Way
  3. Solution Two: the New Easy Way

What Didn't Work

What doesn't work is using the actual Message object like I did in my question. Don't ask me why, it's not documented anywhere, it just doesn't work. Even if you copy all or existing parts of the JSON object from another message via Gmail.Users.Drafts.get(), GAS will still throw an error.

So what goes in is not what gets returned, even though the documentation says otherwise.

Hence the only solution is to use the raw property of the message object, which must be a base-64-encoded string in the RFC2822 format.

Solution One: the Hard Way

Combining the solutions from here and here allowed to create a basic function that generates a draft message with emojis:

function convert(toEmail, fromEmail, subject, body) {
  body = Utilities.base64Encode(body, Utilities.Charset.UTF_8);
  subject = Utilities.base64Encode(subject, Utilities.Charset.UTF_8);
  const boundary = "boundaryboundary";
  const mailData = [
    "MIME-Version: 1.0",
    "To: "   toEmail,
    "From: "   fromEmail,
    "Subject: =?utf-8?B?"   subject   "?=",
    "Content-Type: multipart/alternative; boundary="   boundary,
    "",
    "--"   boundary,
    "Content-Type: text/plain; charset=UTF-8",
    "",
    body,
    "",
    "--"   boundary,
    "Content-Type: text/html; charset=UTF-8",
    "Content-Transfer-Encoding: base64",
    "",
    body,
    "",
    "--"   boundary,
  ].join("\r\n");
  return mailData;
}

function makeApiDraft() {
  const subject = "Hello MimeText World";
  const body = 'This is a plain text message';
  const me = Session.getActiveUser().getEmail();
  const raw = convert('[email protected]', me, subject, body);
  const b64 = Utilities.base64EncodeWebSafe(raw);
  console.log(raw)
  Gmail.Users.Drafts.create({ message: { raw: raw } }, me);
}

Nothing wrong with this solution, it works. However, if you want to go beyond this example, like adding multiple recipients, having different plain text and html bodies, managing attachments, etc., you will have to code it all by hand and that requires understanding the RFC2822 MIMEText format.

Hence enter the new easier solution.

Solution Two: the New Easy Way

I stumbled upon this library that generates MIMEText emails written in Node.js. So I thought perfect. I forked the repo and adapted a few things to make it GAS-compatible, specifically:

  1. Base 64 encoding is done with Utilities.base64Encode() and Utilities.base64EncodeWebSafe()
  2. Attaching files is done by simply passing a GAS DriveApp.File object
  3. I made sure proper MIMEText headers and base 64 encoding were present where they were needed.

And while my pool request is pending, I transpiled the whole thing with Webpack (as the library does have a dependency) and published it as a GAS library under this ID:

1HzFRRghlhuCDl0FUnuE9uKAK39GfeuUJAE3oOsjv74Qjq1UW8YEboEit

Here's an example project that you can use to test it out, but the code is basically as follows:

const testMimeText = () => {
  const { message } = MimeText;
  message.setSender({
    name: 'Dmitry Kostyuk',
    addr: '[email protected]',
  });
  const file = DriveApp.getFileById('1pdMwlGL1WZTbi-Q2-Fc7nBm-9NKphkKg');
  const me = Session.getActiveUser().getEmail();

  message.setRecipient('[email protected]');
  message.setSubject('Hello MimeText World!');
  message.setMessage('This is a plain text message '   getAllEmojis(), 'text/plain');
  message.setMessage('<p>This is an html message</p><p>'   getAllEmojis()   '</p>\r\n\r\n', 'text/html');
  message.setAttachments([file]);

  const raw = message.asEncoded();
  Gmail.Users.Drafts.create({ message: { raw: raw } }, me);
}

const getDriveAuth = () => DriveApp.getRootFolder();

I guess I went down a rabbit hole I never expected to, but I'm pretty happy with how it turned out :)

  • Related