import { getMillionVerifierApi, MILLIONVERIFIER_CREDITS_SCHEMA, MILLIONVERIFIER_CREDITS_WIRE_SCHEMA, MILLIONVERIFIER_EMAIL_VERIFICATION_SCHEMA, MILLIONVERIFIER_EMAIL_VERIFICATION_WIRE_SCHEMA, MILLIONVERIFIER_FILE_ID_SCHEMA, MILLIONVERIFIER_FILE_FILTER_STATUS_SCHEMA, MILLIONVERIFIER_FILE_SCHEMA, MILLIONVERIFIER_FILE_WIRE_SCHEMA, parseMillionVerifierJson, toMillionVerifierFileStatus, } from "@automate.ax/integration-contracts/millionverifier" import * as z from "zod" import { defineAction } from "../../automation/actions" const MILLIONVERIFIER_ACCOUNT = "millionverifier" const MILLIONVERIFIER_DATE_FILTER_SCHEMA = z.union([ z.date(), z.string().regex(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/), ]) const MILLIONVERIFIER_REPORT_FILTER_SCHEMA = z.enum([ "ok", "okAndCatchAll", "unknown", "invalid", "all", "custom", ]) const MILLIONVERIFIER_REPORT_STATUS_SCHEMA = z.enum([ "ok", "catchAll", "unknown", "invalid", "disposable", ]) const MILLIONVERIFIER_FILE_LIST_SCHEMA = z .looseObject({ files: MILLIONVERIFIER_FILE_WIRE_SCHEMA.array(), total: z.number().int().nonnegative(), }) .transform(({ files, total }) => ({ files, total })) const MILLIONVERIFIER_OPERATION_SCHEMA = z.looseObject({ result: z.literal("ok"), }) /** Verifies one email address through MillionVerifier's real-time API. */ export const verifyMillionVerifierEmail = defineAction( "Verify MillionVerifier email", ) .describe( "Verifies one email address through MillionVerifier's real-time API.", ) .account(MILLIONVERIFIER_ACCOUNT) .input( z.object({ email: z.email(), timeoutSeconds: z.number().int().min(2).max(60).prefault(20), }), ) .output(MILLIONVERIFIER_EMAIL_VERIFICATION_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const path = "/api/v3" return await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "single", query: { email: input.email, timeout: input.timeoutSeconds }, }), path, MILLIONVERIFIER_EMAIL_VERIFICATION_WIRE_SCHEMA, ) }) /** Gets the available MillionVerifier credits and plan code. */ export const getMillionVerifierCredits = defineAction( "Get MillionVerifier credits", ) .describe("Gets the available MillionVerifier credits and plan code.") .account(MILLIONVERIFIER_ACCOUNT) .input(z.object({})) .output(MILLIONVERIFIER_CREDITS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => { const path = "/api/v3/credits" return await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "single", }), path, MILLIONVERIFIER_CREDITS_WIRE_SCHEMA, ) }) /** Uploads a text or CSV file for bulk MillionVerifier verification. */ export const uploadMillionVerifierFile = defineAction( "Upload MillionVerifier file", ) .describe("Uploads a text or CSV file for bulk email verification.") .account(MILLIONVERIFIER_ACCOUNT) .input(z.object({ file: z.instanceof(File) })) .output(MILLIONVERIFIER_FILE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/v2/upload" const body = new FormData() body.append("file_contents", input.file, input.file.name) return await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "bulk", body, method: "POST", }), path, MILLIONVERIFIER_FILE_WIRE_SCHEMA, { acceptErrorFieldWhenValid: true }, ) }) /** Gets the current status and counts for one MillionVerifier bulk file. */ export const getMillionVerifierFile = defineAction("Get MillionVerifier file") .describe( "Gets the current status and counts for one bulk verification file.", ) .account(MILLIONVERIFIER_ACCOUNT) .input(z.object({ fileId: MILLIONVERIFIER_FILE_ID_SCHEMA })) .output(MILLIONVERIFIER_FILE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/v2/fileinfo" return await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "bulk", query: { file_id: input.fileId }, }), path, MILLIONVERIFIER_FILE_WIRE_SCHEMA, { acceptErrorFieldWhenValid: true }, ) }) /** Lists MillionVerifier bulk files with provider-supported filters. */ export const listMillionVerifierFiles = defineAction( "List MillionVerifier files", ) .describe( "Lists bulk verification files with status, date, and progress filters.", ) .account(MILLIONVERIFIER_ACCOUNT) .input( z .object({ createdAfter: MILLIONVERIFIER_DATE_FILTER_SCHEMA.optional(), createdBefore: MILLIONVERIFIER_DATE_FILTER_SCHEMA.optional(), fileIds: MILLIONVERIFIER_FILE_ID_SCHEMA.array().optional(), hasError: z.boolean().optional(), limit: z.number().int().min(1).max(50).prefault(50), name: z.string().min(1).optional(), offset: z.number().int().nonnegative().prefault(0), percentFrom: z.number().int().min(0).max(100).optional(), percentTo: z.number().int().min(0).max(100).optional(), statuses: MILLIONVERIFIER_FILE_FILTER_STATUS_SCHEMA.array().optional(), updatedAfter: MILLIONVERIFIER_DATE_FILTER_SCHEMA.optional(), updatedBefore: MILLIONVERIFIER_DATE_FILTER_SCHEMA.optional(), }) .refine( ({ percentFrom, percentTo }) => percentFrom === undefined || percentTo === undefined || percentFrom <= percentTo, { message: "percentFrom must be less than or equal to percentTo.", path: ["percentFrom"], }, ), ) .output( z.object({ files: MILLIONVERIFIER_FILE_SCHEMA.array(), total: z.number() }), ) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/v2/filelist" return await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "bulk", query: { createdate_from: millionVerifierDateFilter(input.createdAfter), createdate_to: millionVerifierDateFilter(input.createdBefore), has_error: input.hasError === undefined ? undefined : input.hasError ? 1 : 0, id: input.fileIds, limit: input.limit, name: input.name, offset: input.offset, percent_from: input.percentFrom, percent_to: input.percentTo, status: input.statuses?.map(toMillionVerifierFileStatus), updated_at_from: millionVerifierDateFilter(input.updatedAfter), updated_at_to: millionVerifierDateFilter(input.updatedBefore), }, }), path, MILLIONVERIFIER_FILE_LIST_SCHEMA, ) }) /** Downloads a filtered MillionVerifier bulk verification report. */ export const downloadMillionVerifierReport = defineAction( "Download MillionVerifier report", ) .describe("Downloads a filtered bulk verification report as a file.") .account(MILLIONVERIFIER_ACCOUNT) .input( z .object({ fileId: MILLIONVERIFIER_FILE_ID_SCHEMA, filter: MILLIONVERIFIER_REPORT_FILTER_SCHEMA.prefault("all"), free: z.boolean().optional(), role: z.boolean().optional(), statuses: MILLIONVERIFIER_REPORT_STATUS_SCHEMA.array().optional(), }) .refine( ({ filter, free, role, statuses }) => filter === "custom" || (free === undefined && role === undefined && statuses === undefined), { message: "statuses, free, and role require the custom filter.", path: ["filter"], }, ), ) .output(z.instanceof(Blob)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/v2/download" const response = await getMillionVerifierApi(account.secret).request(path, { api: "bulk", query: { file_id: input.fileId, filter: input.filter === "okAndCatchAll" ? "ok_and_catch_all" : input.filter, free: input.free === undefined ? undefined : input.free ? 1 : 0, role: input.role === undefined ? undefined : input.role ? 1 : 0, statuses: input.statuses?.map((status) => status === "catchAll" ? "catch_all" : status, ), }, }) if (response.headers.get("content-type")?.includes("json")) { await parseMillionVerifierJson(response, path, z.never()) } return await response.blob() }) /** Stops an in-progress MillionVerifier bulk file. */ export const stopMillionVerifierFile = defineAction("Stop MillionVerifier file") .describe("Stops an in-progress bulk verification file.") .account(MILLIONVERIFIER_ACCOUNT) .input(z.object({ fileId: MILLIONVERIFIER_FILE_ID_SCHEMA })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/stop" await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "bulk", query: { file_id: input.fileId }, }), path, MILLIONVERIFIER_OPERATION_SCHEMA, ) }) /** Deletes one MillionVerifier bulk file. */ export const deleteMillionVerifierFile = defineAction( "Delete MillionVerifier file", ) .describe("Deletes one uploaded bulk verification file.") .account(MILLIONVERIFIER_ACCOUNT) .input(z.object({ fileId: MILLIONVERIFIER_FILE_ID_SCHEMA })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const path = "/bulkapi/v2/delete" await parseMillionVerifierJson( await getMillionVerifierApi(account.secret).request(path, { api: "bulk", query: { file_id: input.fileId }, }), path, MILLIONVERIFIER_OPERATION_SCHEMA, ) }) /** * Formats a Date as MillionVerifier's documented UTC filter timestamp. * * @param value - Date or provider-formatted timestamp. */ function millionVerifierDateFilter(value: Date | string | undefined) { if (!(value instanceof Date)) return value return value.toISOString().slice(0, 19).replace("T", " ") }