UNPKG

@microfox/aws-ses

Version:
293 lines (291 loc) 13.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createSESSdk: () => createSESSdk, default: () => aws_ses_sdk_default }); module.exports = __toCommonJS(index_exports); // src/aws-ses-sdk.ts var import_zod = require("zod"); var import_rest_sdk = require("@microfox/rest-sdk"); var import_crypto = __toESM(require("crypto")); var import_usage_tracker = require("@microfox/usage-tracker"); var SendEmailResultSchema = import_zod.z.object({ MessageId: import_zod.z.string().describe("The unique identifier for the sent email") }).describe("The result of the successful SendEmail operation"); var ResponseMetadataSchema = import_zod.z.object({ RequestId: import_zod.z.string().describe("The unique request identifier") }).describe("Metadata about the AWS SES API request"); var SendEmailResponsePartSchema = import_zod.z.object({ SendEmailResult: SendEmailResultSchema, ResponseMetadata: ResponseMetadataSchema }).describe("The actual response body from the SendEmail API"); var SendEmailResponseSchema = import_zod.z.object({ SendEmailResponse: SendEmailResponsePartSchema }).passthrough().describe("Response from AWS SES SendEmail API"); var SESConfigSchema = import_zod.z.object({ accessKeyId: import_zod.z.string().min(1).describe("AWS access key ID for authentication"), secretAccessKey: import_zod.z.string().min(1).describe("AWS secret access key for authentication"), region: import_zod.z.string().min(1).describe("AWS region where SES is configured (e.g., us-east-1)") }).describe("AWS SES SDK configuration"); var EmailParamsSchema = import_zod.z.object({ sender: import_zod.z.string().email().describe("Email address of the sender (must be verified in AWS SES)"), recipient: import_zod.z.string().email().describe("Email address of the recipient"), displayName: import_zod.z.string().optional().describe( 'Display name for the sender (e.g., "John Doe" <john@example.com>)' ), subject: import_zod.z.string().min(1).describe("Subject line of the email"), bodyText: import_zod.z.string().optional().describe( "Plain text version of the email body (recommended for email clients that cannot display HTML)" ), bodyHtml: import_zod.z.string().optional().describe("HTML version of the email body (for rich text formatting)") }).describe( "Parameters for sending a single email. Only one of bodyText or bodyHtml can be provided" ); var BulkEmailParamsSchema = import_zod.z.object({ sender: import_zod.z.string().email().describe("Email address of the sender (must be verified in AWS SES)"), recipients: import_zod.z.array(import_zod.z.string().email()).min(1).describe("List of recipient email addresses"), displayName: import_zod.z.string().optional().describe( 'Display name for the sender (e.g., "John Doe" <john@example.com>)' ), subject: import_zod.z.string().min(1).describe("Subject line of the email"), bodyText: import_zod.z.string().optional().describe( "Plain text version of the email body (recommended for email clients that cannot display HTML)" ), bodyHtml: import_zod.z.string().optional().describe("HTML version of the email body (for rich text formatting)") }).refine((data) => !(data.bodyText && data.bodyHtml), { message: "Cannot provide both bodyText and bodyHtml at the same time", path: ["bodyContent"] }).refine((data) => data.recipients.length > 0, { message: "Recipients array must contain at least one email address", path: ["recipients"] }).describe( "Parameters for sending bulk emails. Only one of bodyText or bodyHtml can be provided" ); var SignatureInputSchema = import_zod.z.object({ key: import_zod.z.string().describe("AWS secret key for signature generation"), dateStamp: import_zod.z.string().describe("AWS formatted date stamp (YYYYMMDD)"), regionName: import_zod.z.string().describe("AWS region name"), serviceName: import_zod.z.string().describe("AWS service name (ses)") }).describe("Input parameters for AWS signature generation"); var CanonicalRequestInputSchema = import_zod.z.object({ method: import_zod.z.string().describe("HTTP method (POST for SES)"), uri: import_zod.z.string().describe("Request URI"), queryString: import_zod.z.string().describe("URL query string"), headers: import_zod.z.string().describe("Canonical headers string"), signedHeaders: import_zod.z.string().describe("List of signed header names"), payloadHash: import_zod.z.string().describe("SHA256 hash of the request payload") }).describe("Input parameters for creating a canonical request"); var StringToSignInputSchema = import_zod.z.object({ algorithm: import_zod.z.string().describe("AWS signature algorithm (AWS4-HMAC-SHA256)"), requestDate: import_zod.z.string().describe("AWS formatted request date"), credentialScope: import_zod.z.string().describe("AWS credential scope"), canonicalRequest: import_zod.z.string().describe("Canonical request string") }).describe("Input parameters for creating string to sign"); var FormattedDateSchema = import_zod.z.object({ amzDate: import_zod.z.string().describe("AWS formatted date (YYYYMMDDTHHMMSSZ)"), dateStamp: import_zod.z.string().describe("AWS formatted date stamp (YYYYMMDD)") }).describe("AWS formatted date components"); var getSignatureKey = (params) => { const { key, dateStamp, regionName, serviceName } = SignatureInputSchema.parse(params); const kDate = import_crypto.default.createHmac("sha256", `AWS4${key}`).update(dateStamp).digest(); const kRegion = import_crypto.default.createHmac("sha256", kDate).update(regionName).digest(); const kService = import_crypto.default.createHmac("sha256", kRegion).update(serviceName).digest(); const kSigning = import_crypto.default.createHmac("sha256", kService).update("aws4_request").digest(); return kSigning; }; var getCanonicalRequest = (params) => { const { method, uri, queryString, headers, signedHeaders, payloadHash } = CanonicalRequestInputSchema.parse(params); return `${method} ${uri} ${queryString} ${headers} ${signedHeaders} ${payloadHash}`; }; var getStringToSign = (params) => { const { algorithm, requestDate, credentialScope, canonicalRequest } = StringToSignInputSchema.parse(params); return `${algorithm} ${requestDate} ${credentialScope} ${import_crypto.default.createHash("sha256").update(canonicalRequest).digest("hex")}`; }; var getFormattedDate = (date) => { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, "0"); const day = String(date.getUTCDate()).padStart(2, "0"); const hours = String(date.getUTCHours()).padStart(2, "0"); const minutes = String(date.getUTCMinutes()).padStart(2, "0"); const seconds = String(date.getUTCSeconds()).padStart(2, "0"); const amzDate = `${year}${month}${day}T${hours}${minutes}${seconds}Z`; const dateStamp = amzDate.slice(0, 8); return FormattedDateSchema.parse({ amzDate, dateStamp }); }; var flattenParams = (params, prefix = "") => { const flatParams = {}; for (const key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { const value = params[key]; const newKey = prefix ? `${prefix}.${key}` : key; if (typeof value === "object" && !Array.isArray(value)) { Object.assign(flatParams, flattenParams(value, newKey)); } else if (Array.isArray(value)) { value.forEach((item, index) => { Object.assign( flatParams, flattenParams(item, `${newKey}.${index + 1}`) ); }); } else { flatParams[newKey] = String(value); } } } return flatParams; }; var formatEmailParams = (params) => { const validatedParams = EmailParamsSchema.parse(params); const formattedParams = { Action: "SendEmail", Version: "2010-12-01", Source: validatedParams.displayName ? `"${validatedParams.displayName}" <${validatedParams.sender}>` : validatedParams.sender, "Destination.ToAddresses.member.1": validatedParams.recipient, "Message.Subject.Charset": "UTF-8", "Message.Subject.Data": validatedParams.subject }; if (validatedParams.bodyHtml) { formattedParams["Message.Body.Html.Charset"] = "UTF-8"; formattedParams["Message.Body.Html.Data"] = validatedParams.bodyHtml; } if (!validatedParams.bodyHtml && validatedParams.bodyText) { formattedParams["Message.Body.Text.Charset"] = "UTF-8"; formattedParams["Message.Body.Text.Data"] = validatedParams.bodyText; } return flattenParams(formattedParams); }; var getRequestHeaders = (config, params) => { const method = "POST"; const serviceName = "ses"; const host = `email.${config.region}.amazonaws.com`; const { amzDate, dateStamp } = getFormattedDate(/* @__PURE__ */ new Date()); const payload = new URLSearchParams(params).toString(); const payloadHash = import_crypto.default.createHash("sha256").update(payload).digest("hex"); const canonicalUri = "/"; const canonicalQuerystring = ""; const canonicalHeaders = `content-type:application/x-www-form-urlencoded host:${host} x-amz-date:${amzDate} `; const signedHeaders = "content-type;host;x-amz-date"; const canonicalRequest = getCanonicalRequest({ method, uri: canonicalUri, queryString: canonicalQuerystring, headers: canonicalHeaders, signedHeaders, payloadHash }); const algorithm = "AWS4-HMAC-SHA256"; const credentialScope = `${dateStamp}/${config.region}/${serviceName}/aws4_request`; const stringToSign = getStringToSign({ algorithm, requestDate: amzDate, credentialScope, canonicalRequest }); const signingKey = getSignatureKey({ key: config.secretAccessKey, dateStamp, regionName: config.region, serviceName }); const signature = import_crypto.default.createHmac("sha256", signingKey).update(stringToSign).digest("hex"); const authorizationHeader = `${algorithm} Credential=${config.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`; return { "Content-Type": "application/x-www-form-urlencoded", "X-Amz-Date": amzDate, Authorization: authorizationHeader, Accept: "application/json", Host: host }; }; var SESSdkSchema = import_zod.z.object({ sendEmail: import_zod.z.any().describe("Send a single email using AWS SES"), sendBulkEmails: import_zod.z.any().describe("Send multiple emails in parallel using AWS SES") }).describe("AWS SES SDK interface"); var createSESSdk = (config) => { const validatedConfig = SESConfigSchema.parse(config); const sendEmail = async (params) => { const validatedParams = EmailParamsSchema.parse(params); const formattedParams = formatEmailParams(validatedParams); const headers = getRequestHeaders(validatedConfig, formattedParams); const sdk = (0, import_rest_sdk.createRestSDK)({ baseUrl: `https://email.${validatedConfig.region}.amazonaws.com`, headers }); try { const response = await sdk.post("/", formattedParams, { contentType: "application/x-www-form-urlencoded" }).json(); const tracker = (0, import_usage_tracker.createDefaultMicrofoxUsageTracker)(); tracker == null ? void 0 : tracker.trackApi1Usage("aws-ses", "outboundEmail", { requestCount: 1 }); return SendEmailResponseSchema.parse(response); } catch (error) { throw new Error( `Failed to send email: ${error instanceof Error ? error.message : String(error)}` ); } }; const sendBulkEmails = async (params) => { const validatedParams = BulkEmailParamsSchema.parse(params); const { recipients, ...rest } = validatedParams; const emailPromises = recipients.map( (recipient) => sendEmail({ ...rest, recipient }) ); const responses = await Promise.all(emailPromises); return import_zod.z.array(SendEmailResponseSchema).parse(responses); }; return SESSdkSchema.parse({ sendEmail, sendBulkEmails }); }; var aws_ses_sdk_default = createSESSdk; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createSESSdk }); //# sourceMappingURL=index.js.map