Skip to content

Instantly share code, notes, and snippets.

@tanaikech
Last active March 22, 2024 19:33
Show Gist options
  • Save tanaikech/44e055214ab470c9b3143a469d7a7d21 to your computer and use it in GitHub Desktop.
Save tanaikech/44e055214ab470c9b3143a469d7a7d21 to your computer and use it in GitHub Desktop.
Sending Multiple Emails using Batch Request with Gmail API using Google Apps Script

Sending Multiple Emails using Batch Request with Gmail API using Google Apps Script

This is a sample script for sending multiple emails using the batch request with Gmail API using Google Apps Script. When multiple emails are sent using "GmailApp.sendEmail" and "MailApp.sendEmail", a loop is used. But in this case, the process cost becomes high. In this post, I would like to introduce the sample script for reducing the process cost under this situation. Gmail API can be requested with the batch request. The batch request can be processed with the asynchronous process. By this, I thought that the process cost for sending multiple emails. So, this sample script sends multiple emails using the batch request with Gmail API.

Sample script

This sample script uses a Google Apps Script library of BatchRequest. So, before you use this script, please install a Google Apps Script library of BatchRequest. You can see how to install this library at here.

And, please enable Gmail API at Advanced Google services.

And please set obj and if you want to attachment files, please set the file IDs of attachmentFileIds.

const convert_ = ({
  to,
  cc,
  bcc,
  fromName,
  fromEmail,
  subject,
  textBody,
  htmlBody,
  attachments,
}) => {
  if (!to) throw new Error("Please set 'to'.");
  const obj = [`MIME-Version: 1.0`, `To: ${to}`];
  if (cc) obj.push(`CC: ${cc}`);
  if (bcc) obj.push(`BCC: ${bcc}`);
  if ((fromName && fromEmail) || fromEmail) {
    obj.push(
      fromName && fromEmail ? `From: "${fromName}" <${fromEmail}>` : fromEmail
    );
  }
  const boundary1 = "boundaryboundary001";
  const boundary2 = "boundaryboundary002";
  if (attachments && attachments.length > 0) {
    obj.push(
      `Subject: =?UTF-8?B?${Utilities.base64Encode(
        subject || "",
        Utilities.Charset.UTF_8
      )}?=`,
      `Content-Type: multipart/mixed; boundary=${boundary1}`,
      ``,
      `--${boundary1}`,
      `Content-Type: multipart/alternative; boundary=${boundary2}`,
      ``,
      `--${boundary2}`,
      `Content-Type: text/plain; charset=UTF-8`,
      ``,
      textBody || "",
      `--${boundary2}`,
      `Content-Type: text/html; charset=UTF-8`,
      `Content-Transfer-Encoding: base64`,
      ``,
      Utilities.base64Encode(htmlBody || "", Utilities.Charset.UTF_8),
      `--${boundary2}--`
    );
    attachments.forEach((f) => {
      obj.push(
        `--${boundary1}`,
        `Content-Type: ${f.mimeType}; charset=UTF-8; name="${f.filename}"`,
        `Content-Transfer-Encoding: base64`,
        ``,
        f.data,
        `--${boundary1}`
      );
    });
  } else {
    obj.push(
      `Subject: =?UTF-8?B?${Utilities.base64Encode(
        subject || "",
        Utilities.Charset.UTF_8
      )}?=`,
      `Content-Type: multipart/alternative; boundary=${boundary1}`,
      ``,
      `--${boundary1}`,
      `Content-Type: text/plain; charset=UTF-8`,
      ``,
      textBody || "",
      `--${boundary1}`,
      `Content-Type: text/html; charset=UTF-8`,
      `Content-Transfer-Encoding: base64`,
      ``,
      Utilities.base64Encode(htmlBody || "", Utilities.Charset.UTF_8),
      `--${boundary1}`
    );
  }
  return Utilities.base64EncodeWebSafe(obj.join("\r\n") + "--");
};

// Please run this function.
function main() {
  const attachmentFileIds = [
    "### file ID1 of attachment file ###",
    "### file ID1 of attachment file ###",
  ];
  const attachmentFiles = attachmentFileIds.map((id) => {
    const file = DriveApp.getFileById(id);
    return {
      filename: file.getName(),
      mimeType: file.getMimeType(),
      data: Utilities.base64Encode(file.getBlob().getBytes()),
    };
  });
  const obj = [
    {
      to: "### email address 1 ###",
      cc: "###",
      fromEmail: "###",
      fromName: "###",
      subject: "sample mail 1",
      textBody: "Hello World",
      htmlBody: "<p>Hello World</p>",
    },
    {
      to: "### email address 2 ###",
      bcc: "###",
      subject: "sample mail 2",
      textBody: "Hello World",
      attachments: attachmentFiles,
    },
  ];
  const raws = obj.map((e) => convert_(e));
  const requests = raws.map((raw) => ({
    method: "POST",
    endpoint: "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
    requestBody: { raw: raw },
  }));
  const res = BatchRequest.EDo({
    batchPath: "batch/gmail/v1",
    accessToken: ScriptApp.getOAuthToken(),
    requests: requests,
  });
  console.log(res);
}
  • When you run this script, 2 emails are sent.

Note

  • When you change the endpoint from https://gmail.googleapis.com/gmail/v1/users/me/messages/send to https://gmail.googleapis.com/gmail/v1/users/me/drafts, the draft emails are created.

  • When you use this script, please be careful about the limitation of Gmail API. Ref1 and Ref2

  • This is a simple sample script. So please modify it for your actual situation.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment