import escapeHtml from "escape-html" import { convert } from "html-to-text" import type { Address } from "postal-mime" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { EMAIL_ORIGIN_TOKEN } from "../../../lib/email-origin" import { buildRawMessage, formatAddress, getGmailApi, parseRawMessage, toMessageMetadata, } from "@automate.ax/integration-contracts/gmail" import { GMAIL_SEND_AND_READ_REQUIREMENT } from "../lib/scopes" import { ATTACHMENT_INPUT_SCHEMA, COMPOSE_OPTIONS_SCHEMA, MESSAGE_METADATA_SCHEMA, RECIPIENTS_SCHEMA, } from "@automate.ax/integration-contracts/gmail" /** * Forwards a Gmail message with optional introductory content. * * Original attachments are preserved by default. Text and HTML introductions * receive the same automatic plain-text fallback as newly composed messages. */ export const forwardGmailMessage = defineAction("Forward Gmail message") .describe("Forwards a Gmail message with optional introductory content.") .account("google", GMAIL_SEND_AND_READ_REQUIREMENT) .input( COMPOSE_OPTIONS_SCHEMA.extend({ /** Optional HTML introduction inserted before the forwarded message. */ html: z.string().optional(), /** Whether to preserve the original message's attachments. */ includeAttachments: z.boolean().optional(), /** Immutable Gmail ID of the message to forward. */ messageId: z.string().min(1), /** * Optional subject override; defaults to the original subject prefixed * with `Fwd:`. */ subject: z.string().optional(), /** Optional plain-text introduction inserted before the forwarded message. */ text: z.string().optional(), /** One or more primary recipients. */ to: RECIPIENTS_SCHEMA, }), ) .output(MESSAGE_METADATA_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input, runtime }) => { const gmailApi = getGmailApi(account.secret) const { data: original } = await gmailApi.users.messages.get({ format: "raw", id: input.messageId, userId: "me", }) if (!original.raw) throw new Error("Gmail did not return the raw message.") const parsed = await parseRawMessage(original.raw) const originalSubject = parsed.subject ?? "" const forwardedHeaders = [ "---------- Forwarded message ---------", `From: ${formatAddresses(parsed.from)}`, `Date: ${parsed.date ?? ""}`, `Subject: ${originalSubject}`, `To: ${formatAddresses(parsed.to)}`, ] const originalText = parsed.text ?? (parsed.html === undefined ? undefined : convert(parsed.html, { wordwrap: false })) const introHtml = input.html ?? (input.text === undefined ? undefined : textToHtml(input.text)) const originalHtml = parsed.html ?? (parsed.text === undefined ? undefined : textToHtml(parsed.text)) // Keep the generated MIME alternatives named so their fallback behavior is readable. const html = introHtml || originalHtml ? [ introHtml, `