import * as z from "zod" import { defineAction } from "../../automation/actions" import { GRAPH_MAIL_FOLDER_SCHEMA, OUTLOOK_EMAIL_ADDRESS_SCHEMA, OUTLOOK_IMPORTANCE_SCHEMA, OUTLOOK_MAIL_FOLDER_SCHEMA, getOutlookGraphApi, listOutlookGraphCollection, } from "@automate.ax/integration-contracts/outlook" import { OUTLOOK_MAIL_METADATA_REQUIREMENT, OUTLOOK_MAIL_READ_REQUIREMENT, OUTLOOK_MAIL_WRITE_REQUIREMENT, OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT, OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT, } from "./lib/scopes" const MAX_LIMIT = 10_000 const DEFAULT_LIMIT = 100 const FOLDER_INPUT_SCHEMA = z.object({ /** Folder display path, ID, or well-known name. */ folder: z.string().trim().min(1), }) const MESSAGE_INPUT_SCHEMA = z.object({ /** Opaque Outlook message identifier. */ messageId: z.string().min(1), }) const CATEGORY_COLOR_SCHEMA = z.enum([ "none", "preset0", "preset1", "preset2", "preset3", "preset4", "preset5", "preset6", "preset7", "preset8", "preset9", "preset10", "preset11", "preset12", "preset13", "preset14", "preset15", "preset16", "preset17", "preset18", "preset19", "preset20", "preset21", "preset22", "preset23", "preset24", ]) const CATEGORY_SCHEMA = z.object({ categoryId: z.string(), color: CATEGORY_COLOR_SCHEMA, displayName: z.string(), }) const GRAPH_CATEGORY_SCHEMA = z .looseObject({ color: CATEGORY_COLOR_SCHEMA, displayName: z.string(), id: z.string(), }) .transform(({ id, ...category }) => ({ ...category, categoryId: id })) const RULE_WRITE_EMAIL_ADDRESS_SCHEMA = z.object({ address: z.email(), name: z.string().optional(), }) const RULE_WRITE_RECIPIENT_SCHEMA = z.object({ emailAddress: RULE_WRITE_EMAIL_ADDRESS_SCHEMA, }) const RULE_RECIPIENT_SCHEMA = z.object({ emailAddress: z.object({ address: z.string().nullish(), name: z.string().nullish(), }), }) const JSON_RECORD_SCHEMA = z.record(z.string(), z.json()) const RULE_PREDICATE_SHAPE = { bodyContains: z.string().array().optional(), bodyOrSubjectContains: z.string().array().optional(), categories: z.string().array().optional(), hasAttachments: z.boolean().optional(), headerContains: z.string().array().optional(), importance: OUTLOOK_IMPORTANCE_SCHEMA.optional(), isApprovalRequest: z.boolean().optional(), isAutomaticForward: z.boolean().optional(), isAutomaticReply: z.boolean().optional(), isEncrypted: z.boolean().optional(), isMeetingRequest: z.boolean().optional(), isMeetingResponse: z.boolean().optional(), isNonDeliveryReport: z.boolean().optional(), isPermissionControlled: z.boolean().optional(), isReadReceipt: z.boolean().optional(), isSigned: z.boolean().optional(), isVoicemail: z.boolean().optional(), messageActionFlag: z .enum([ "any", "call", "doNotForward", "followUp", "fyi", "forward", "noResponseNecessary", "read", "reply", "replyToAll", "review", ]) .optional(), notSentToMe: z.boolean().optional(), recipientContains: z.string().array().optional(), senderContains: z.string().array().optional(), sensitivity: z .enum(["normal", "personal", "private", "confidential"]) .optional(), sentCcMe: z.boolean().optional(), sentOnlyToMe: z.boolean().optional(), sentToMe: z.boolean().optional(), sentToOrCcMe: z.boolean().optional(), subjectContains: z.string().array().optional(), withinSizeRange: z .object({ maximumSize: z.number().int(), minimumSize: z.number().int() }) .optional(), } const RULE_PREDICATES_SCHEMA = z.object({ ...RULE_PREDICATE_SHAPE, fromAddresses: RULE_RECIPIENT_SCHEMA.array().optional(), sentToAddresses: RULE_RECIPIENT_SCHEMA.array().optional(), }) const RULE_WRITE_PREDICATES_SCHEMA = z.object({ ...RULE_PREDICATE_SHAPE, fromAddresses: RULE_WRITE_RECIPIENT_SCHEMA.array().optional(), sentToAddresses: RULE_WRITE_RECIPIENT_SCHEMA.array().optional(), }) const RULE_ACTION_SHAPE = { assignCategories: z.string().array().optional(), copyToFolder: z.string().optional(), delete: z.boolean().optional(), markAsRead: z.boolean().optional(), markImportance: OUTLOOK_IMPORTANCE_SCHEMA.optional(), moveToFolder: z.string().optional(), permanentDelete: z.boolean().optional(), stopProcessingRules: z.boolean().optional(), } const RULE_ACTIONS_SCHEMA = z.object({ ...RULE_ACTION_SHAPE, forwardAsAttachmentTo: RULE_RECIPIENT_SCHEMA.array().optional(), forwardTo: RULE_RECIPIENT_SCHEMA.array().optional(), redirectTo: RULE_RECIPIENT_SCHEMA.array().optional(), }) const RULE_WRITE_ACTIONS_SCHEMA = z.object({ ...RULE_ACTION_SHAPE, forwardAsAttachmentTo: RULE_WRITE_RECIPIENT_SCHEMA.array().optional(), forwardTo: RULE_WRITE_RECIPIENT_SCHEMA.array().optional(), redirectTo: RULE_WRITE_RECIPIENT_SCHEMA.array().optional(), }) const RULE_SCHEMA = z.object({ actions: RULE_ACTIONS_SCHEMA.nullish().transform((value) => value ?? {}), conditions: RULE_PREDICATES_SCHEMA.nullish().transform( (value) => value ?? {}, ), displayName: z.string().nullish(), exceptions: RULE_PREDICATES_SCHEMA.nullish().transform( (value) => value ?? undefined, ), hasError: z.boolean().nullish(), isEnabled: z.boolean().nullish(), isReadOnly: z.boolean().nullish(), ruleId: z.string().nullish(), sequence: z.number().int().nullish(), }) const RULE_ITEM_SCHEMA = RULE_SCHEMA.extend({ ruleId: z.string() }) const GRAPH_RULE_SCHEMA = z .looseObject({ actions: RULE_ACTIONS_SCHEMA.nullish(), conditions: RULE_PREDICATES_SCHEMA.nullish(), displayName: z.string().nullish(), exceptions: RULE_PREDICATES_SCHEMA.nullish(), hasError: z.boolean().nullish(), id: z.string().nullish(), isEnabled: z.boolean().nullish(), isReadOnly: z.boolean().nullish(), sequence: z.number().int().nullish(), }) .transform(({ id, ...rule }) => ({ ...rule, ruleId: id })) const GRAPH_RULE_ITEM_SCHEMA = GRAPH_RULE_SCHEMA.pipe(RULE_ITEM_SCHEMA) const RULE_WRITE_SCHEMA = z.object({ actions: RULE_WRITE_ACTIONS_SCHEMA, conditions: RULE_WRITE_PREDICATES_SCHEMA.prefault({}), displayName: z.string().min(1), exceptions: RULE_WRITE_PREDICATES_SCHEMA.optional(), isEnabled: z.boolean().prefault(true), sequence: z.number().int().nonnegative(), }) const TIME_ZONE_SCHEMA = z.object({ name: z.string() }) const DATE_TIME_TIME_ZONE_SCHEMA = z.object({ dateTime: z.string(), timeZone: z.string(), }) const AUTOMATIC_REPLIES_SCHEMA = z.object({ externalAudience: z.enum(["none", "contactsOnly", "all"]).nullish(), externalReplyMessage: z.string().nullish(), internalReplyMessage: z.string().nullish(), scheduledEndDateTime: DATE_TIME_TIME_ZONE_SCHEMA.nullish(), scheduledStartDateTime: DATE_TIME_TIME_ZONE_SCHEMA.nullish(), status: z.enum(["disabled", "alwaysEnabled", "scheduled"]).nullish(), }) const DAY_OF_WEEK_SCHEMA = z.enum([ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", ]) const MAILBOX_SETTINGS_SCHEMA = z.object({ archiveFolder: z.string().nullish(), automaticRepliesSetting: AUTOMATIC_REPLIES_SCHEMA.nullish(), dateFormat: z.string().nullish(), delegateMeetingMessageDeliveryOptions: z .enum([ "sendToDelegateAndInformationToPrincipal", "sendToDelegateAndPrincipal", "sendToDelegateOnly", ]) .nullish(), language: z .object({ displayName: z.string().nullish(), locale: z.string() }) .nullish(), timeFormat: z.string().nullish(), timeZone: z.string().nullish(), userPurpose: z .enum([ "user", "linked", "shared", "room", "equipment", "others", "unknownFutureValue", ]) .nullish(), workingHours: z .object({ daysOfWeek: DAY_OF_WEEK_SCHEMA.array(), endTime: z.string(), startTime: z.string(), timeZone: TIME_ZONE_SCHEMA, }) .nullish(), }) const MAILBOX_SETTINGS_WRITE_SCHEMA = MAILBOX_SETTINGS_SCHEMA.pick({ automaticRepliesSetting: true, dateFormat: true, delegateMeetingMessageDeliveryOptions: true, language: true, timeFormat: true, timeZone: true, workingHours: true, }).strict() const MAIL_TIP_SCHEMA = z.object({ automaticReplies: JSON_RECORD_SCHEMA.nullish(), customMailTip: z.string().nullish(), deliveryRestricted: z.boolean().nullish(), emailAddress: OUTLOOK_EMAIL_ADDRESS_SCHEMA.nullish(), error: JSON_RECORD_SCHEMA.nullish(), externalMemberCount: z.number().int().nullish(), isModerated: z.boolean().nullish(), mailboxFull: z.boolean().nullish(), maxMessageSize: z.number().int().nullish(), recipientScope: z.string().nullish(), recipientSuggestions: JSON_RECORD_SCHEMA.array().nullish(), totalMemberCount: z.number().int().nullish(), }) const MAIL_TIP_OPTION_SCHEMA = z.enum([ "automaticReplies", "mailboxFullStatus", "customMailTip", "externalMemberCount", "totalMemberCount", "maxMessageSize", "deliveryRestriction", "moderationStatus", "recipientScope", "recipientSuggestions", ]) /** Gets one Outlook mail folder. */ export const getOutlookMailFolder = defineAction("Get Outlook mail folder") .describe("Gets one Outlook mail folder by path, ID, or well-known name.") .account("microsoft", OUTLOOK_MAIL_METADATA_REQUIREMENT) .input(FOLDER_INPUT_SCHEMA) .output(OUTLOOK_MAIL_FOLDER_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { request } = getOutlookGraphApi(account.secret) return GRAPH_MAIL_FOLDER_SCHEMA.parse( await request( `/me/mailFolders/${encodeURIComponent(await resolveFolderId(request, input.folder))}`, ), ) }) /** Creates an Outlook mail folder. */ export const createOutlookMailFolder = defineAction( "Create Outlook mail folder", ) .describe("Creates a top-level or child Outlook mail folder.") .account("microsoft", OUTLOOK_MAIL_WRITE_REQUIREMENT) .input( z.object({ displayName: z.string().trim().min(1), parentFolder: z.string().trim().min(1).optional(), }), ) .output(OUTLOOK_MAIL_FOLDER_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { request } = getOutlookGraphApi(account.secret) const parentId = input.parentFolder ? await resolveFolderId(request, input.parentFolder) : undefined return GRAPH_MAIL_FOLDER_SCHEMA.parse( await request( parentId ? `/me/mailFolders/${encodeURIComponent(parentId)}/childFolders` : "/me/mailFolders", { body: { displayName: input.displayName }, method: "POST" }, ), ) }) /** Updates an Outlook mail folder name. */ export const updateOutlookMailFolder = defineAction( "Update Outlook mail folder", ) .describe("Renames an Outlook mail folder.") .account("microsoft", OUTLOOK_MAIL_WRITE_REQUIREMENT) .input(FOLDER_INPUT_SCHEMA.extend({ displayName: z.string().trim().min(1) })) .output(OUTLOOK_MAIL_FOLDER_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { request } = getOutlookGraphApi(account.secret) return GRAPH_MAIL_FOLDER_SCHEMA.parse( await request( `/me/mailFolders/${encodeURIComponent(await resolveFolderId(request, input.folder))}`, { body: { displayName: input.displayName }, method: "PATCH" }, ), ) }) /** Deletes an Outlook mail folder. */ export const deleteOutlookMailFolder = folderVoidAction( "Delete Outlook mail folder", "Deletes an Outlook mail folder.", "DELETE", ) /** Permanently deletes an Outlook mail folder. */ export const permanentlyDeleteOutlookMailFolder = folderVoidAction( "Permanently delete Outlook mail folder", "Permanently deletes an Outlook mail folder without moving it to Deleted Items.", "POST", "/permanentDelete", ) /** Copies an Outlook mail folder. */ export const copyOutlookMailFolder = folderDestinationAction( "Copy Outlook mail folder", "Copies an Outlook mail folder beneath another folder.", "copy", ) /** Moves an Outlook mail folder. */ export const moveOutlookMailFolder = folderDestinationAction( "Move Outlook mail folder", "Moves an Outlook mail folder beneath another folder.", "move", ) /** Downloads one Outlook message as MIME. */ export const getOutlookMessageMime = defineAction("Get Outlook message MIME") .describe("Downloads an Outlook message in RFC 5322 MIME format.") .account("microsoft", OUTLOOK_MAIL_READ_REQUIREMENT) .input(MESSAGE_INPUT_SCHEMA) .output(z.instanceof(File)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const response = await getOutlookGraphApi(account.secret).fetch( `/me/messages/${encodeURIComponent(input.messageId)}/$value`, ) if (!response.ok) throw new Error( `Microsoft Graph MIME download failed with ${response.status} ${response.statusText}.`, ) return new File([await response.blob()], `${input.messageId}.eml`, { type: response.headers.get("content-type") ?? "message/rfc822", }) }) /** Permanently deletes one Outlook message. */ export const permanentlyDeleteOutlookMessage = defineAction( "Permanently delete Outlook message", ) .describe( "Permanently deletes an Outlook message without moving it to Deleted Items.", ) .account("microsoft", OUTLOOK_MAIL_WRITE_REQUIREMENT) .input(MESSAGE_INPUT_SCHEMA) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getOutlookGraphApi(account.secret).request( `/me/messages/${encodeURIComponent(input.messageId)}/permanentDelete`, { method: "POST" }, ) }) /** Lists Outlook master categories. */ export const listOutlookCategories = categoryListAction() /** Gets one Outlook master category. */ export const getOutlookCategory = categoryItemAction( "Get Outlook category", "Gets one Outlook master category.", "GET", ) /** Creates an Outlook master category. */ export const createOutlookCategory = categoryItemAction( "Create Outlook category", "Creates an Outlook master category.", "POST", ) /** Updates an Outlook master category. */ export const updateOutlookCategory = categoryItemAction( "Update Outlook category", "Updates an Outlook master category.", "PATCH", ) /** Deletes an Outlook master category. */ export const deleteOutlookCategory = categoryDeleteAction() /** Lists Inbox rules. */ export const listOutlookMessageRules = defineAction( "List Outlook message rules", ) .describe("Lists Inbox rules in evaluation order.") .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT) .input(z.object({ limit: z.number().int().min(1).max(MAX_LIMIT).optional() })) .output(RULE_ITEM_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => listOutlookGraphCollection( getOutlookGraphApi(account.secret).request, "/me/mailFolders/inbox/messageRules", GRAPH_RULE_ITEM_SCHEMA, input.limit ?? DEFAULT_LIMIT, ), ) /** Gets one Inbox rule. */ export const getOutlookMessageRule = ruleItemAction( "Get Outlook Inbox rule", "Gets one Inbox rule.", "GET", ) /** Creates one Inbox rule. */ export const createOutlookMessageRule = ruleItemAction( "Create Outlook Inbox rule", "Creates an Inbox rule.", "POST", ) /** Updates one Inbox rule. */ export const updateOutlookMessageRule = ruleItemAction( "Update Outlook Inbox rule", "Updates an Inbox rule.", "PATCH", ) /** Deletes one Inbox rule. */ export const deleteOutlookMessageRule = ruleDeleteAction() /** Gets the connected mailbox settings. */ export const getOutlookMailboxSettings = defineAction( "Get Outlook mailbox settings", ) .describe( "Gets locale, timezone, working hours, and automatic reply settings.", ) .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT) .input(z.object({})) .output(MAILBOX_SETTINGS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => MAILBOX_SETTINGS_SCHEMA.parse( await getOutlookGraphApi(account.secret).request("/me/mailboxSettings"), ), ) /** Updates the connected mailbox settings. */ export const updateOutlookMailboxSettings = defineAction( "Update Outlook mailbox settings", ) .describe( "Updates locale, timezone, working hours, or automatic reply settings.", ) .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT) .input(MAILBOX_SETTINGS_WRITE_SCHEMA) .output(MAILBOX_SETTINGS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => MAILBOX_SETTINGS_SCHEMA.parse( await getOutlookGraphApi(account.secret).request("/me/mailboxSettings", { body: input, method: "PATCH", }), ), ) /** Gets MailTips for recipients. */ export const getOutlookMailTips = defineAction("Get Outlook MailTips") .describe("Gets delivery warnings and automatic replies for recipients.") .account("microsoft", OUTLOOK_MAIL_READ_REQUIREMENT) .input( z.object({ emailAddresses: z.email().array().min(1).max(100), mailTipsOptions: MAIL_TIP_OPTION_SCHEMA.array().min(1).optional(), }), ) .output(MAIL_TIP_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler( async ({ account, input }) => z.object({ value: MAIL_TIP_SCHEMA.array() }).parse( await getOutlookGraphApi(account.secret).request("/me/getMailTips", { body: { EmailAddresses: input.emailAddresses, MailTipsOptions: input.mailTipsOptions?.join(","), }, method: "POST", }), ).value, ) /** * Builds a mail-folder action that returns no value. * * @param name - Public action name. * @param description - Public action description. * @param method - Graph request method. * @param suffix - Path appended to the folder resource. */ function folderVoidAction( name: string, description: string, method: string, suffix = "", ) { return defineAction(name) .describe(description) .account("microsoft", OUTLOOK_MAIL_WRITE_REQUIREMENT) .input(FOLDER_INPUT_SCHEMA) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { request } = getOutlookGraphApi(account.secret) await request( `/me/mailFolders/${encodeURIComponent(await resolveFolderId(request, input.folder))}${suffix}`, { method }, ) }) } /** * Builds a mail-folder copy or move action. * * @param name - Public action name. * @param description - Public action description. * @param operation - Graph folder operation. */ function folderDestinationAction( name: string, description: string, operation: "copy" | "move", ) { return defineAction(name) .describe(description) .account("microsoft", OUTLOOK_MAIL_WRITE_REQUIREMENT) .input( FOLDER_INPUT_SCHEMA.extend({ destinationFolder: z.string().trim().min(1), }), ) .output(OUTLOOK_MAIL_FOLDER_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { request } = getOutlookGraphApi(account.secret) const [folderId, destinationId] = await Promise.all([ resolveFolderId(request, input.folder), resolveFolderId(request, input.destinationFolder), ]) return GRAPH_MAIL_FOLDER_SCHEMA.parse( await request( `/me/mailFolders/${encodeURIComponent(folderId)}/${operation}`, { body: { destinationId }, method: "POST" }, ), ) }) } /** Builds the Outlook category list action. */ function categoryListAction() { return defineAction("List Outlook categories") .describe("Lists Outlook master categories.") .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT) .input(z.object({})) .output(CATEGORY_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => listOutlookGraphCollection( getOutlookGraphApi(account.secret).request, "/me/outlook/masterCategories", GRAPH_CATEGORY_SCHEMA, MAX_LIMIT, ), ) } /** * Builds an Outlook category item action. * * @param name - Public action name. * @param description - Public action description. * @param method - Graph request method. */ function categoryItemAction( name: string, description: string, method: "GET" | "POST" | "PATCH", ) { const isCreate = method === "POST" // The request method determines the public input contract. const inputSchema = isCreate ? z.object({ color: CATEGORY_COLOR_SCHEMA, displayName: z.string().min(1) }) : method === "PATCH" ? z.object({ categoryId: z.string().min(1), color: CATEGORY_COLOR_SCHEMA, }) : z.object({ categoryId: z.string().min(1) }) return defineAction(name) .describe(description) .account( "microsoft", method !== "GET" ? OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT : OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT, ) .input(inputSchema) .output(CATEGORY_SCHEMA) .retry({ replaySafety: method === "GET" || method === "PATCH" ? "safe" : "unsafe", }) .handler(async ({ account, input }) => { // Only create and update requests send a category body. const body = isCreate ? input : method === "PATCH" ? { color: "color" in input ? input.color : undefined } : undefined return GRAPH_CATEGORY_SCHEMA.parse( await getOutlookGraphApi(account.secret).request( isCreate ? "/me/outlook/masterCategories" : `/me/outlook/masterCategories/${encodeURIComponent("categoryId" in input ? input.categoryId : "")}`, { body, method }, ), ) }) } /** Builds the Outlook category delete action. */ function categoryDeleteAction() { return defineAction("Delete Outlook category") .describe("Deletes an Outlook master category.") .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT) .input(z.object({ categoryId: z.string().min(1) })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getOutlookGraphApi(account.secret).request( `/me/outlook/masterCategories/${encodeURIComponent(input.categoryId)}`, { method: "DELETE" }, ) }) } /** * Builds an Outlook Inbox rule item action. * * @param name - Public action name. * @param description - Public action description. * @param method - Graph request method. */ function ruleItemAction( name: string, description: string, method: "GET" | "POST" | "PATCH", ) { const isCreate = method === "POST" // The request method determines the public input contract. const inputSchema = isCreate ? RULE_WRITE_SCHEMA : method === "PATCH" ? RULE_WRITE_SCHEMA.partial().extend({ ruleId: z.string().min(1) }) : z.object({ ruleId: z.string().min(1) }) return defineAction(name) .describe(description) .account( "microsoft", method === "GET" ? OUTLOOK_MAILBOX_SETTINGS_READ_REQUIREMENT : OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT, ) .input(inputSchema) .output(RULE_ITEM_SCHEMA) .retry({ replaySafety: method === "GET" || method === "PATCH" ? "safe" : "unsafe", }) .handler(async ({ account, input }) => { const ruleId = "ruleId" in input ? input.ruleId : undefined let body: unknown if (isCreate) body = input else if (method === "PATCH" && "ruleId" in input) { const { ruleId: _, ...updates } = input body = updates } let path = "/me/mailFolders/inbox/messageRules" if (!isCreate) { if (!ruleId) throw new Error("An Outlook Inbox rule ID is required.") path = `${path}/${encodeURIComponent(ruleId)}` } return GRAPH_RULE_ITEM_SCHEMA.parse( await getOutlookGraphApi(account.secret).request(path, { body, method, }), ) }) } /** Builds the Outlook Inbox-rule delete action. */ function ruleDeleteAction() { return defineAction("Delete Outlook Inbox rule") .describe("Deletes one Inbox rule.") .account("microsoft", OUTLOOK_MAILBOX_SETTINGS_WRITE_REQUIREMENT) .input(z.object({ ruleId: z.string().min(1) })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getOutlookGraphApi(account.secret).request( `/me/mailFolders/inbox/messageRules/${encodeURIComponent(input.ruleId)}`, { method: "DELETE" }, ) }) } /** * Resolves an Outlook folder name, path, or ID to its Graph ID. * * @param request - Authenticated Graph request function. * @param reference - Folder name, path, or ID. */ async function resolveFolderId( request: ReturnType["request"], reference: string, ) { const direct = await request( `/me/mailFolders/${encodeURIComponent(reference)}?$select=id`, ) .then((value) => z.object({ id: z.string() }).parse(value).id) .catch(() => undefined) if (direct) return direct // Folder paths support nested names while detecting ambiguous leaf names. const folders = await listFolderPaths(request) const normalized = reference.trim().toLowerCase() const matches = folders.filter( (folder) => folder.path.toLowerCase() === normalized || folder.displayName.toLowerCase() === normalized, ) if (matches.length === 1) return matches[0]!.folderId if (matches.length > 1) throw new Error( `Outlook folder "${reference}" is ambiguous; use its full path or ID.`, ) throw new Error(`Outlook folder "${reference}" was not found.`) } /** * Lists Outlook folders with their full display paths. * * @param request - Authenticated Graph request function. * @param parent - Parent folder traversal state. * @param parent.folderId - Parent folder Graph ID. * @param parent.path - Parent folder display path. */ async function listFolderPaths( request: ReturnType["request"], parent?: { folderId: string; path: string }, ): Promise<{ displayName: string; folderId: string; path: string }[]> { const folders = await listOutlookGraphCollection( request, parent ? `/me/mailFolders/${encodeURIComponent(parent.folderId)}/childFolders` : "/me/mailFolders", GRAPH_MAIL_FOLDER_SCHEMA, MAX_LIMIT, ) return [ ...folders.map((folder) => ({ displayName: folder.displayName, folderId: folder.folderId, path: parent ? `${parent.path}/${folder.displayName}` : folder.displayName, })), ...( await Promise.all( folders .filter((folder) => folder.childFolderCount > 0) .map((folder) => listFolderPaths(request, { folderId: folder.folderId, path: parent ? `${parent.path}/${folder.displayName}` : folder.displayName, }), ), ) ).flat(), ] }