@microfox/aws-ses
Version:
AWS SES SDK for Microfox
293 lines (291 loc) • 13.5 kB
JavaScript
;
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