Edits history of script submission #23294 for ' Send Email (gmail)'

  • bun
    One script reply has been approved by the moderators
    Ap­pro­ved
    import * as wmill from "windmill-client"
    import { S3Object } from "windmill-client"
    
    /**
     * Send Email
     * Send an email from the connected Gmail account, with optional cc/bcc, HTML body, file attachments (from Windmill object storage) and a `from` send-as alias (see List Send-As Aliases; an unverified alias falls back to the primary address). To reply in a thread, pass the original's `thread_id` and its `Message-ID` header as `in_reply_to`, and keep the same subject. Requires gmail.send (the default scope), gmail.compose or gmail.modify on the connection.
     */
    export async function main(
      auth: RT.Gmail,
      to: string,
      subject: string,
      body: string,
      cc: string | undefined,
      bcc: string | undefined,
      from: string | undefined,
      is_html: boolean = false,
      attachments: S3Object[] | undefined,
      thread_id: string | undefined,
      in_reply_to: string | undefined
    ) {
      const boundary = `wm_${crypto.randomUUID()}`
      const headers = [
        from ? `From: ${from}` : undefined,
        `To: ${to}`,
        cc ? `Cc: ${cc}` : undefined,
        bcc ? `Bcc: ${bcc}` : undefined,
        `Subject: =?UTF-8?B?${b64(subject)}?=`,
        in_reply_to ? `In-Reply-To: ${in_reply_to}` : undefined,
        in_reply_to ? `References: ${in_reply_to}` : undefined,
        "MIME-Version: 1.0",
      ].filter((h) => h !== undefined)
      const text = [
        `Content-Type: text/${is_html ? "html" : "plain"}; charset="UTF-8"`,
        "Content-Transfer-Encoding: base64",
        "",
        wrap(b64(body)),
      ].join("\r\n")
    
      let mime: string
      if (!attachments?.length) {
        mime = `${headers.join("\r\n")}\r\n${text}`
      } else {
        const parts = [text]
        for (const file of attachments) {
          const bytes = await wmill.loadS3File(file)
          if (!bytes) throw new Error(`Attachment not found: ${file.s3}`)
          const name = file.s3.split("/").pop()!
          parts.push(
            [
              `Content-Type: ${Bun.file(name).type}; name="${name}"`,
              `Content-Disposition: attachment; filename="${name}"`,
              "Content-Transfer-Encoding: base64",
              "",
              wrap(b64(bytes)),
            ].join("\r\n")
          )
        }
        mime = [
          ...headers,
          `Content-Type: multipart/mixed; boundary="${boundary}"`,
          "",
          ...parts.map((p) => `--${boundary}\r\n${p}`),
          `--${boundary}--`,
        ].join("\r\n")
      }
    
      const response = await fetch(
        "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${auth.token}`,
            "Content-Type": "application/json",
            Accept: "application/json",
          },
          body: JSON.stringify({
            raw: Buffer.from(mime).toString("base64url"),
            threadId: thread_id || undefined,
          }),
        }
      )
      if (!response.ok) {
        throw new Error(`${response.status} ${await response.text()}`)
      }
    
      return await response.json()
    }
    
    function b64(s: string | Uint8Array) {
      return Buffer.from(s).toString("base64")
    }
    
    function wrap(s: string) {
      return s.replace(/.{76}/g, "$&\r\n")
    }
    

    Submitted by hugo989 6 hours ago