Skip to content

AWS.SES reference

Source: src/AWS/SES/AccountSettings.ts

Account-level Amazon SES v2 settings — an account/region singleton that manages account-wide sending status, the account suppression list, and Virtual Deliverability Manager (VDM) configuration.

Only the aspects you specify are managed: omit sendingEnabled, suppression, or vdm to leave that setting untouched.

Deleting this resource is a no-op — it leaves the account settings exactly as they are. Unlike a normal resource there is nothing to tear down: these are account-global toggles with no single safe default, and resetting them (e.g. disabling sending or clearing the suppression list) would affect live mail beyond this stack. Change the props and re-deploy to adjust them.

Enable VDM with Engagement Tracking

import * as SES from "alchemy/AWS/SES";
const settings = yield* SES.AccountSettings("Account", {
vdm: {
enabled: "ENABLED",
dashboardEngagementMetrics: "ENABLED",
},
});

Configure the Suppression List

const settings = yield* SES.AccountSettings("Account", {
suppression: { reasons: ["BOUNCE", "COMPLAINT"] },
});

Enable Guardian Optimized Shared Delivery

const settings = yield* SES.AccountSettings("Account", {
vdm: {
enabled: "ENABLED",
guardianOptimizedSharedDelivery: "ENABLED",
},
});

AccountSettings: Pausing Account-Wide Sending

Section titled “AccountSettings: Pausing Account-Wide Sending”

Stop All Sending for the Account

// WARNING: this halts every outbound email in the account, including mail
// sent by stacks and systems outside this one. Prefer a configuration
// set's sendingEnabled to pause a single sending path.
const settings = yield* SES.AccountSettings("Account", {
sendingEnabled: false,
});

Manage Only VDM and Leave Sending Alone

// Omitted aspects are never touched — this deploy will not read or write
// the account's sending status or suppression list.
const settings = yield* SES.AccountSettings("Account", {
vdm: { enabled: "ENABLED" },
});

Source: src/AWS/SES/ActiveReceiptRuleSet.ts

The account’s active Amazon SES receipt rule set — the single rule set (per account, per region) that SES actually evaluates against inbound mail.

This is an account-level singleton pointer, not a container: only one rule set can be active at a time. Deleting this resource deactivates email receiving (clears the pointer) only when the account is still pointed at the rule set this resource set; if something else has since become active, the delete is a no-op.

ActiveReceiptRuleSet: Activating a Rule Set

Section titled “ActiveReceiptRuleSet: Activating a Rule Set”
import * as SES from "alchemy/AWS/SES";
const ruleSet = yield* SES.ReceiptRuleSet("Inbound", {});
const active = yield* SES.ActiveReceiptRuleSet("Active", {
ruleSetName: ruleSet.ruleSetName,
});

Source: src/AWS/SES/BatchGetMetricData.ts

Runtime binding for sesv2:BatchGetMetricData.

Fetches up to 10 aggregated deliverability metric time-series in one call — sends, deliveries, bounces, complaints, opens, clicks — over a date range, optionally sliced by dimension (ISP, configuration set, etc.). Requires VDM (Virtual Deliverability Manager) to be enabled; a malformed query surfaces the typed BadRequestException. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.BatchGetMetricDataHttp).

BatchGetMetricData: Deliverability Insights

Section titled “BatchGetMetricData: Deliverability Insights”
// init — account-level binding, no resource argument
const getMetrics = yield* SES.BatchGetMetricData();
// runtime
const { Results } = yield* getMetrics({
Queries: [
{
Id: "sends",
Namespace: "VDM",
Metric: "SEND",
StartDate: new Date(Date.now() - 7 * 24 * 3600 * 1000),
EndDate: new Date(),
},
],
});

Source: src/AWS/SES/ConfigurationSet.ts

An Amazon SES v2 configuration set — a named group of sending options (TLS policy, reputation metrics, suppression overrides) that you apply to outbound email, either per-message or as an identity’s default.

Attach event destinations with SES.ConfigurationSetEventDestination to stream send/delivery/bounce/complaint events to SNS, EventBridge, or CloudWatch.

ConfigurationSet: Creating Configuration Sets

Section titled “ConfigurationSet: Creating Configuration Sets”

Basic Configuration Set

import * as SES from "alchemy/AWS/SES";
const configSet = yield* SES.ConfigurationSet("Default", {});

Require TLS and Publish Reputation Metrics

const configSet = yield* SES.ConfigurationSet("Strict", {
tlsPolicy: "REQUIRE",
reputationMetricsEnabled: true,
});

Suppress Bounces and Complaints

const configSet = yield* SES.ConfigurationSet("Suppressing", {
suppressedReasons: ["BOUNCE", "COMPLAINT"],
});
// The redirect domain must be a verified subdomain you own with a valid
// certificate. Omit `tracking` entirely to keep SES's current setting.
const configSet = yield* SES.ConfigurationSet("Tracked", {
tracking: {
customRedirectDomain: "links.example.com",
httpsPolicy: "REQUIRE",
},
});

ConfigurationSet: Virtual Deliverability Manager

Section titled “ConfigurationSet: Virtual Deliverability Manager”
// Requires account-level VDM — see SES.AccountSettings.
const configSet = yield* SES.ConfigurationSet("Measured", {
vdm: {
dashboardEngagementMetrics: "ENABLED",
guardianOptimizedSharedDelivery: "ENABLED",
},
});
const topic = yield* SNS.Topic("EmailEvents", {});
const destination = yield* SES.ConfigurationSetEventDestination("ToSns", {
configurationSetName: configSet.configurationSetName,
matchingEventTypes: ["SEND", "DELIVERY", "BOUNCE", "COMPLAINT"],
snsDestination: { topicArn: topic.topicArn },
});

Source: src/AWS/SES/ConfigurationSetEventDestination.ts

An event destination on an SES v2 configuration set — streams send/delivery/bounce/complaint (and open/click) events to SNS, EventBridge, or CloudWatch.

ConfigurationSetEventDestination: Creating Event Destinations

Section titled “ConfigurationSetEventDestination: Creating Event Destinations”

Publish Bounce and Complaint Events to SNS

import * as SES from "alchemy/AWS/SES";
import * as SNS from "alchemy/AWS/SNS";
const topic = yield* SNS.Topic("EmailEvents", {});
const configSet = yield* SES.ConfigurationSet("Default", {});
const destination = yield* SES.ConfigurationSetEventDestination("ToSns", {
configurationSetName: configSet.configurationSetName,
matchingEventTypes: ["BOUNCE", "COMPLAINT"],
snsDestination: { topicArn: topic.topicArn },
});

Publish Metrics to CloudWatch

const metrics = yield* SES.ConfigurationSetEventDestination("Metrics", {
configurationSetName: configSet.configurationSetName,
matchingEventTypes: ["SEND", "DELIVERY"],
cloudWatchDestination: {
dimensionConfigurations: [
{
dimensionName: "campaign",
dimensionValueSource: "MESSAGE_TAG",
defaultDimensionValue: "none",
},
],
},
});

Source: src/AWS/SES/Contact.ts

An Amazon SES v2 contact — a single email address on a SES.ContactList, with its own topic subscription preferences and unsubscribe state.

Basic Contact

import * as SES from "alchemy/AWS/SES";
const list = yield* SES.ContactList("Newsletter", {});
const contact = yield* SES.Contact("Subscriber", {
contactListName: list.contactListName,
emailAddress: "reader@example.com",
});

Contact with Topic Preferences

const contact = yield* SES.Contact("Subscriber", {
contactListName: list.contactListName,
emailAddress: "reader@example.com",
topicPreferences: [
{ TopicName: "product-updates", SubscriptionStatus: "OPT_IN" },
{ TopicName: "promotions", SubscriptionStatus: "OPT_OUT" },
],
});

Unsubscribe a Contact from Everything

// unsubscribeAll overrides every per-topic preference.
const contact = yield* SES.Contact("Subscriber", {
contactListName: list.contactListName,
emailAddress: "reader@example.com",
unsubscribeAll: true,
});
// Serialized to the JSON string SES stores; re-ordering the keys is not a
// change, so this does not churn on every deploy.
const contact = yield* SES.Contact("Subscriber", {
contactListName: list.contactListName,
emailAddress: "reader@example.com",
attributes: { plan: "pro", signupSource: "docs" },
});

Source: src/AWS/SES/ContactList.ts

An Amazon SES v2 contact list — a named audience of email contacts with subscription topics, used with SES’s list-management and unsubscribe handling.

Add contacts with SES.Contact. Deleting the list deletes all of its contacts.

SES allows only one contact list per AWS account, so renaming a list replaces it by deleting the old list (and its contacts) before creating the new one — a create-then-delete replacement would exceed the account limit.

Basic Contact List

import * as SES from "alchemy/AWS/SES";
const list = yield* SES.ContactList("Newsletter", {
description: "Weekly product newsletter",
});

Contact List with Topics

const list = yield* SES.ContactList("Newsletter", {
topics: [
{
TopicName: "product-updates",
DisplayName: "Product Updates",
DefaultSubscriptionStatus: "OPT_IN",
},
{
TopicName: "promotions",
DisplayName: "Promotions",
DefaultSubscriptionStatus: "OPT_OUT",
},
],
});

Contact List with Tags

const list = yield* SES.ContactList("Newsletter", {
tags: { Team: "growth", Environment: "prod" },
});
const list = yield* SES.ContactList("Newsletter", {
topics: [
{
TopicName: "product-updates",
DisplayName: "Product Updates",
DefaultSubscriptionStatus: "OPT_IN",
},
],
});
for (const email of ["a@example.com", "b@example.com"]) {
yield* SES.Contact(`Subscriber-${email}`, {
contactListName: list.contactListName,
emailAddress: email,
topicPreferences: [
{ TopicName: "product-updates", SubscriptionStatus: "OPT_IN" },
],
});
}

Source: src/AWS/SES/CustomVerificationEmailTemplate.ts

An Amazon SES v2 custom verification email template — the branded email SES sends when you verify a new email-address identity via SendCustomVerificationEmail.

Creating, reading, updating, and deleting the template works on any account. Actually sending a custom verification email requires the account to be out of the SES sandbox (production access).

CustomVerificationEmailTemplate: Creating Templates

Section titled “CustomVerificationEmailTemplate: Creating Templates”

Branded Verification Email

import * as SES from "alchemy/AWS/SES";
const template = yield* SES.CustomVerificationEmailTemplate("Verify", {
fromEmailAddress: "verify@example.com",
templateSubject: "Please confirm your email",
templateContent:
"<html><body>Click the link to verify your address.</body></html>",
successRedirectionURL: "https://example.com/verified",
failureRedirectionURL: "https://example.com/verify-failed",
});

Explicit Template Name

// Without templateName a deterministic name is derived from app/stage/id.
const template = yield* SES.CustomVerificationEmailTemplate("Verify", {
templateName: "onboarding-verification",
fromEmailAddress: "verify@example.com",
templateSubject: "Please confirm your email",
templateContent:
"<html><body>Click the link to verify your address.</body></html>",
successRedirectionURL: "https://example.com/verified",
failureRedirectionURL: "https://example.com/verify-failed",
});

CustomVerificationEmailTemplate: Sending the Verification Email

Section titled “CustomVerificationEmailTemplate: Sending the Verification Email”
// init — account-level binding, no resource argument
const sendVerification = yield* SES.SendCustomVerificationEmail();
// runtime — SES emails the branded template to the address, and the
// address becomes a verified identity once the recipient clicks through.
const { MessageId } = yield* sendVerification({
EmailAddress: "new-user@example.com",
TemplateName: yield* template.templateName,
});

Source: src/AWS/SES/DedicatedIpPool.ts

An Amazon SES v2 dedicated IP pool — a named group of dedicated IP addresses used to send email, so you can isolate the sending reputation of different kinds of mail (e.g. marketing vs. transactional).

STANDARDMANAGED is an in-place scaling change. MANAGEDSTANDARD is not supported by AWS and replaces the pool.

Standard Pool

import * as SES from "alchemy/AWS/SES";
const pool = yield* SES.DedicatedIpPool("Marketing", {
scalingMode: "STANDARD",
});

Managed Pool

const pool = yield* SES.DedicatedIpPool("Transactional", {
scalingMode: "MANAGED",
});

Explicit Pool Name

// Without poolName a deterministic lowercase name is derived from
// app/stage/id. Pool names allow lowercase letters, numbers, and dashes.
const pool = yield* SES.DedicatedIpPool("Marketing", {
poolName: "acme-marketing",
});

DedicatedIpPool: Changing the Scaling Mode

Section titled “DedicatedIpPool: Changing the Scaling Mode”
// STANDARD -> MANAGED is applied in place — the pool keeps its name and
// its dedicated IPs.
const pool = yield* SES.DedicatedIpPool("Marketing", {
scalingMode: "MANAGED", // was "STANDARD"
});
// MANAGED -> STANDARD has no AWS API, so it REPLACES the pool: a new pool
// is created and the old one deleted, dropping its dedicated IPs.
// Give each kind of mail its own pool so a marketing reputation hit
// cannot take down password resets.
const marketing = yield* SES.DedicatedIpPool("Marketing", {
scalingMode: "STANDARD",
});
const transactional = yield* SES.DedicatedIpPool("Transactional", {
scalingMode: "MANAGED",
});

Source: src/AWS/SES/DeleteSuppressedDestination.ts

Runtime binding for sesv2:DeleteSuppressedDestination.

Removes an email address from the account-level suppression list — e.g. after a recipient re-subscribes or a bounce is resolved. Fails with the typed NotFoundException tag when the address is not on the list. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.DeleteSuppressedDestinationHttp).

DeleteSuppressedDestination: Suppression List

Section titled “DeleteSuppressedDestination: Suppression List”
// init — account-level binding, no resource argument
const unsuppress = yield* SES.DeleteSuppressedDestination();
// runtime
yield* unsuppress({ EmailAddress: "resubscribed@example.com" }).pipe(
Effect.catchTag("NotFoundException", () => Effect.void),
);

Source: src/AWS/SES/EmailIdentity.ts

An Amazon SES v2 email identity — a verified email address or domain that you send email from.

Creating the identity starts verification: email-address identities receive a verification email, and domain identities get Easy DKIM tokens (exposed as the dkimTokens attribute) to publish as CNAME records. The identity is usable for sending once verificationStatus is SUCCESS.

Domain Identity

import * as SES from "alchemy/AWS/SES";
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
});
// publish identity.dkimTokens as CNAME records to verify

Email Address Identity

const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "hello@example.com",
});
// SES emails hello@example.com a verification link

EmailIdentity: Configuration Set Association

Section titled “EmailIdentity: Configuration Set Association”
const configSet = yield* SES.ConfigurationSet("Tracking", {});
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
configurationSetName: configSet.configurationSetName,
});

Turn Easy DKIM Signing Off

// Omit the prop entirely to leave SES's current setting alone.
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
dkimSigningEnabled: false,
});

Stop Forwarding Bounces and Complaints by Email

// Turn this off once a configuration set event destination is handling
// bounces and complaints, so they stop arriving as mail.
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
feedbackForwardingEnabled: false,
});
// mailFromDomain must be a subdomain of the identity, and needs MX and
// SPF records published before SES will use it.
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
mailFromDomain: "bounce.mail.example.com",
// Reject rather than silently falling back to the SES default when the
// MX record cannot be read.
mailFromBehaviorOnMxFailure: "REJECT_MESSAGE",
});
// init
const sendEmail = yield* SES.SendEmail(identity);
// runtime
const result = yield* sendEmail({
FromEmailAddress: "hello@mail.example.com",
Destination: { ToAddresses: ["customer@example.com"] },
Content: {
Simple: {
Subject: { Data: "Welcome!" },
Body: { Text: { Data: "Hello from SES." } },
},
},
});

Source: src/AWS/SES/EmailIdentityPolicy.ts

An Amazon SES v2 sending-authorization policy attached to an email identity — lets the identity owner authorize other AWS accounts or IAM principals to send email using the identity.

SES stores the policy document as JSON; Alchemy serializes the typed IAM policy at the API boundary and compares its normalized content for drift.

Authorize Another Account to Send

import * as SES from "alchemy/AWS/SES";
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.example.com",
});
const policy = yield* SES.EmailIdentityPolicy("AllowPartner", {
emailIdentity: identity.emailIdentity,
policy: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: "arn:aws:iam::111122223333:root" },
Action: ["ses:SendEmail"],
Resource: identity.identityArn,
},
],
},
});

Explicit Policy Name

// Without policyName a deterministic name is derived from app/stage/id.
const policy = yield* SES.EmailIdentityPolicy("AllowPartner", {
emailIdentity: identity.emailIdentity,
policyName: "partner-send",
policy: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: "arn:aws:iam::111122223333:root" },
Action: ["ses:SendEmail"],
Resource: identity.identityArn,
},
],
},
});

Restrict the Grant with Conditions

const policy = yield* SES.EmailIdentityPolicy("AllowPartnerScoped", {
emailIdentity: identity.emailIdentity,
policy: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: "arn:aws:iam::111122223333:root" },
Action: ["ses:SendEmail", "ses:SendRawEmail"],
Resource: identity.identityArn,
Condition: {
StringEquals: { "ses:FromAddress": "noreply@mail.example.com" },
},
},
],
},
});

Several Policies on One Identity

// Each policy is a separate resource keyed by its own name.
for (const partner of ["111122223333", "444455556666"]) {
yield* SES.EmailIdentityPolicy(`Allow${partner}`, {
emailIdentity: identity.emailIdentity,
policyName: `partner-${partner}`,
policy: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: `arn:aws:iam::${partner}:root` },
Action: ["ses:SendEmail"],
Resource: identity.identityArn,
},
],
},
});
}

Source: src/AWS/SES/EmailTemplate.ts

An Amazon SES v2 email template — reusable subject/text/HTML content with {{variable}} personalization tags, rendered server-side when you send templated email.

import * as SES from "alchemy/AWS/SES";
const template = yield* SES.EmailTemplate("Welcome", {
subject: "Welcome, {{name}}!",
text: "Hi {{name}}, thanks for signing up.",
html: "<h1>Hi {{name}}</h1><p>Thanks for signing up.</p>",
});
const sendEmail = yield* SES.SendEmail(identity);
const result = yield* sendEmail({
Destination: { ToAddresses: ["customer@example.com"] },
Content: {
Template: {
TemplateName: "my-welcome-template",
TemplateData: JSON.stringify({ name: "Ada" }),
},
},
});

Source: src/AWS/SES/GetAccount.ts

Runtime binding for sesv2:GetAccount.

Retrieves the SES account’s sending status in the current region — the send quota, whether sending is enabled, and whether the account has production access (or is still in the sandbox). Useful to check remaining quota before a large send. Account-level operation — invoked with no arguments. Provide the implementation with Effect.provide(AWS.SES.GetAccountHttp).

// init — account-level binding, no resource argument
const getAccount = yield* SES.GetAccount();
// runtime
const account = yield* getAccount();
// account.SendQuota?.Max24HourSend, account.ProductionAccessEnabled

Source: src/AWS/SES/GetBlacklistReports.ts

Runtime binding for sesv2:GetBlacklistReports.

Retrieves, per dedicated-IP address, the list of anti-spam blacklists (RBLs) that IP currently appears on, with the observation time. Useful for a reputation dashboard over the account’s dedicated IPs. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.GetBlacklistReportsHttp).

GetBlacklistReports: Deliverability Insights

Section titled “GetBlacklistReports: Deliverability Insights”
// init — account-level binding, no resource argument
const getBlacklists = yield* SES.GetBlacklistReports();
// runtime — the dedicated IP addresses to check
const { BlacklistReport } = yield* getBlacklists({
BlacklistItemNames: ["192.0.2.1"],
});

Source: src/AWS/SES/GetDomainStatisticsReport.ts

Runtime binding for sesv2:GetDomainStatisticsReport.

Retrieves inbox-placement and engagement statistics for a domain identity over a date range — the data behind the SES deliverability dashboard. Requires the deliverability dashboard subscription; an unknown domain surfaces the typed NotFoundException. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.GetDomainStatisticsReportHttp).

GetDomainStatisticsReport: Deliverability Insights

Section titled “GetDomainStatisticsReport: Deliverability Insights”
// init — account-level binding, no resource argument
const getReport = yield* SES.GetDomainStatisticsReport();
// runtime
const report = yield* getReport({
Domain: "example.com",
StartDate: new Date(Date.now() - 7 * 24 * 3600 * 1000),
EndDate: new Date(),
});

Source: src/AWS/SES/GetMessageInsights.ts

Runtime binding for sesv2:GetMessageInsights.

Retrieves the delivery insights SES tracked for a single sent message — the per-recipient event timeline (send, delivery, open, click, bounce, complaint) plus the resolved subject, from-address, and tags. Requires VDM (Virtual Deliverability Manager) to be enabled and the message to have been sent within the retention window; an unknown message id fails with the typed NotFoundException. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.GetMessageInsightsHttp).

GetMessageInsights: Deliverability Insights

Section titled “GetMessageInsights: Deliverability Insights”
// init — account-level binding, no resource argument
const getInsights = yield* SES.GetMessageInsights();
// runtime — MessageId returned by a prior SendEmail
const { Insights } = yield* getInsights({ MessageId: messageId });

Source: src/AWS/SES/GetSuppressedDestination.ts

Runtime binding for sesv2:GetSuppressedDestination.

Retrieves a specific address from the account-level suppression list — check whether (and why) an address is suppressed before attempting a send. Fails with the typed NotFoundException tag when the address is not on the list. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.GetSuppressedDestinationHttp).

GetSuppressedDestination: Suppression List

Section titled “GetSuppressedDestination: Suppression List”
// init — account-level binding, no resource argument
const getSuppressed = yield* SES.GetSuppressedDestination();
// runtime
const { SuppressedDestination } = yield* getSuppressed({
EmailAddress: "bouncing@example.com",
});
// SuppressedDestination.Reason — "BOUNCE" | "COMPLAINT"

Source: src/AWS/SES/ListSuppressedDestinations.ts

Runtime binding for sesv2:ListSuppressedDestinations.

Lists the addresses on the account-level suppression list, optionally filtered by reason and date range. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.ListSuppressedDestinationsHttp).

ListSuppressedDestinations: Suppression List

Section titled “ListSuppressedDestinations: Suppression List”
// init — account-level binding, no resource argument
const listSuppressed = yield* SES.ListSuppressedDestinations();
// runtime
const { SuppressedDestinationSummaries } = yield* listSuppressed({
Reasons: ["BOUNCE"],
});

Source: src/AWS/SES/MultiRegionEndpoint.ts

An Amazon SES v2 multi-region endpoint (global endpoint) — a single sending endpoint that splits email traffic across a primary region (where the endpoint is created) and one or more secondary regions, improving resilience and deliverability.

There is no update API, so any change to the name or routes replaces the endpoint.

Two-Region Endpoint

import * as SES from "alchemy/AWS/SES";
// The primary region is wherever the stack deploys; the route adds a
// secondary region.
const endpoint = yield* SES.MultiRegionEndpoint("Global", {
regions: ["eu-west-1"],
});

Three-Region Endpoint

// Traffic is split across the primary region plus every listed route.
const endpoint = yield* SES.MultiRegionEndpoint("Global", {
regions: ["eu-west-1", "ap-southeast-2"],
});

Explicit Endpoint Name

const endpoint = yield* SES.MultiRegionEndpoint("Global", {
endpointName: "acme-global",
regions: ["eu-west-1"],
});
import * as sesv2 from "@distilled.cloud/aws/sesv2";
import * as Effect from "effect/Effect";
import * as Schedule from "effect/Schedule";
const endpoint = yield* SES.MultiRegionEndpoint("Global", {
regions: ["eu-west-1"],
});
// Reconcile returns as soon as SES accepts the create, so status is
// usually CREATING. Poll yourself when you need to block on readiness.
const ready = yield* sesv2
.getMultiRegionEndpoint({ EndpointName: yield* endpoint.endpointName })
.pipe(
Effect.repeat({
schedule: Schedule.spaced("30 seconds"),
until: (r) => r.Status === "READY",
times: 40,
}),
);

Source: src/AWS/SES/PutSuppressedDestination.ts

Runtime binding for sesv2:PutSuppressedDestination.

Adds an email address to the account-level suppression list — the data-plane half of bounce/complaint handling: consume feedback events and suppress the offending address so SES never attempts it again. Account-level operation. Provide the implementation with Effect.provide(AWS.SES.PutSuppressedDestinationHttp).

PutSuppressedDestination: Suppression List

Section titled “PutSuppressedDestination: Suppression List”
// init — account-level binding, no resource argument
const suppress = yield* SES.PutSuppressedDestination();
// runtime
yield* suppress({
EmailAddress: "bouncing@example.com",
Reason: "BOUNCE",
});

Source: src/AWS/SES/ReceiptFilter.ts

An Amazon SES receipt IP address filter — an account-level allow/block rule for the source IP of inbound mail. Block filters take precedence over allow filters.

Filters are immutable: there is no update API, so any change to the name or the IP rule replaces the filter.

Block a CIDR Range

import * as SES from "alchemy/AWS/SES";
const filter = yield* SES.ReceiptFilter("BlockBadActors", {
ipFilter: { policy: "Block", cidr: "10.0.0.0/24" },
});

Allow a Single Address

const filter = yield* SES.ReceiptFilter("AllowPartner", {
ipFilter: { policy: "Allow", cidr: "192.0.2.10" },
});

Source: src/AWS/SES/ReceiptRule.ts

An Amazon SES receipt rule — a matcher plus an ordered list of actions that SES applies to inbound email received through the parent SES.ReceiptRuleSet.

Actions are passed as the raw distilled action shapes (no marshalling): the caller supplies bucket names, topic ARNs, and function ARNs directly.

Deliver Matching Mail to S3

import * as SES from "alchemy/AWS/SES";
const ruleSet = yield* SES.ReceiptRuleSet("Inbound", {});
const rule = yield* SES.ReceiptRule("ToBucket", {
ruleSetName: ruleSet.ruleSetName,
recipients: ["support@example.com"],
actions: [
{ S3Action: { BucketName: "my-inbound-mail" } },
],
});

Invoke a Lambda and Add a Header

const rule = yield* SES.ReceiptRule("Process", {
ruleSetName: ruleSet.ruleSetName,
tlsPolicy: "Require",
scanEnabled: true,
actions: [
{ AddHeaderAction: { HeaderName: "X-Inbound", HeaderValue: "ses" } },
{ LambdaAction: { FunctionArn: fn.functionArn, InvocationType: "Event" } },
],
});
// A BounceAction's Sender must be a verified SES identity — SES rejects the
// rule with IdentityNotVerified at create/update time otherwise.
const first = yield* SES.ReceiptRule("First", {
ruleSetName: ruleSet.ruleSetName,
actions: [{ StopAction: { Scope: "RuleSet" } }],
});
const second = yield* SES.ReceiptRule("Second", {
ruleSetName: ruleSet.ruleSetName,
after: first.ruleName,
actions: [{ BounceAction: {
SmtpReplyCode: "550",
Message: "Mailbox does not exist",
Sender: "mailer-daemon@example.com",
} }],
});

Source: src/AWS/SES/ReceiptRuleSet.ts

An Amazon SES receipt rule set — the ordered container for the receipt rules that decide what happens to inbound email (deliver to S3, invoke a Lambda, publish to SNS, bounce, etc.).

A rule set is an empty container on creation; add SES.ReceiptRules to it and point the account at it with SES.ActiveReceiptRuleSet to start processing mail. Email receiving is only available in a subset of regions (e.g. us-east-1, us-west-2, eu-west-1).

Basic Rule Set

import * as SES from "alchemy/AWS/SES";
const ruleSet = yield* SES.ReceiptRuleSet("Inbound", {});

Named Rule Set

const ruleSet = yield* SES.ReceiptRuleSet("Inbound", {
ruleSetName: "my-inbound-rules",
});
const ruleSet = yield* SES.ReceiptRuleSet("Inbound", {});
yield* SES.ActiveReceiptRuleSet("Active", {
ruleSetName: ruleSet.ruleSetName,
});

Source: src/AWS/SES/RenderEmailTemplate.ts

Runtime binding for sesv2:TestRenderEmailTemplate.

Bind this operation to an EmailTemplate inside a function runtime to get a callable that renders the template server-side with the given personalization data — useful for previews and for validating template data before a send. The binding grants the function ses:TestRenderEmailTemplate scoped to the template.

// init
const renderTemplate = yield* SES.RenderEmailTemplate(template);
// runtime
const { RenderedTemplate } = yield* renderTemplate({
TemplateData: JSON.stringify({ name: "Ada" }),
});

Source: src/AWS/SES/SendBounce.ts

Runtime binding for ses:SendBounce (classic SES).

Generates and sends a bounce message to the sender of an email you received through SES receiving — the data-plane counterpart to SES.ReceiptRule inbound processing. You can only bounce a message within 24 hours of receiving it, and only for mail SES actually received.

Account-level operation with no resource-level IAM scoping. Provide the implementation with Effect.provide(AWS.SES.SendBounceHttp).

// init — account-level binding, no resource argument
const sendBounce = yield* SES.SendBounce();
// runtime — inside the Lambda that a ReceiptRule invokes
yield* sendBounce({
OriginalMessageId: messageId,
BounceSender: "mailer-daemon@example.com",
BouncedRecipientInfoList: [
{
Recipient: "nobody@example.com",
BounceType: "DoesNotExist",
},
],
});

Source: src/AWS/SES/SendBulkEmail.ts

Runtime binding for sesv2:SendBulkEmail.

Bind this operation to an EmailIdentity (and optionally a ConfigurationSet) inside a function runtime to get a callable that sends one templated message to up to 50 destinations per call, with per-entry replacement data. The binding grants the function the send actions scoped to the identity, the account’s templates, and the configuration set.

Bulk sends always render a template — reference one via DefaultContent.Template.

// init
const sendBulkEmail = yield* SES.SendBulkEmail(identity, configSet);
// runtime
const result = yield* sendBulkEmail({
DefaultContent: {
Template: {
TemplateName: "welcome-template",
TemplateData: JSON.stringify({ name: "friend" }),
},
},
BulkEmailEntries: [
{
Destination: { ToAddresses: ["ada@example.com"] },
ReplacementEmailContent: {
ReplacementTemplate: {
ReplacementTemplateData: JSON.stringify({ name: "Ada" }),
},
},
},
],
});
// result.BulkEmailEntryResults

Source: src/AWS/SES/SendCustomVerificationEmail.ts

Runtime binding for sesv2:SendCustomVerificationEmail.

Sends the branded verification email defined by a CustomVerificationEmailTemplate to a new email address, kicking off the address-verification flow. Pass the template name and the address to verify; SES takes the FROM address from the template.

Bind it to the identity the function is allowed to VERIFY — SES authorizes this action against the identity of the address in the request, not against the template’s FROM identity. Bind a domain to allow any address at it, or a single address to allow exactly that one. Without a bound identity the binding would let any holder send verification mail to arbitrary addresses. The identity is not injected into the request. Optionally bind a ConfigurationSet, which is injected into each request.

The identity may be a managed EmailIdentity or a plain reference — { emailIdentity: "signups.example.com" }. The reference form creates no resource edge and takes no ownership, so it can scope the grant to an identity the stack does not manage without a destroy ever deleting it.

Provide the implementation with Effect.provide(AWS.SES.SendCustomVerificationEmailHttp).

Note: actually sending a custom verification email requires the account to be out of the SES sandbox — in the sandbox the call fails with the typed BadRequestException.

SendCustomVerificationEmail: Verifying Addresses

Section titled “SendCustomVerificationEmail: Verifying Addresses”

Send a Custom Verification Email

// init — the function may verify addresses at this identity's domain
const sendVerification = yield* SES.SendCustomVerificationEmail(identity);
// runtime
const { MessageId } = yield* sendVerification({
EmailAddress: "new-user@example.com",
TemplateName: yield* template.templateName,
});

Scope to a Domain the Stack Does Not Manage

// Any address at signups.example.com may be verified. No resource edge and
// no ownership — nothing is created, adopted, or destroyed.
const sendVerification = yield* SES.SendCustomVerificationEmail({
emailIdentity: "signups.example.com",
});

Attribute the Send to a Configuration Set

// ConfigurationSetName is injected into every request
const sendVerification = yield* SES.SendCustomVerificationEmail(
identity,
configSet,
);

Source: src/AWS/SES/SendEmail.ts

Runtime binding for sesv2:SendEmail.

Bind this operation to an EmailIdentity (and optionally a ConfigurationSet) inside a function runtime to get a callable that sends simple, raw, or templated email. The binding grants the function ses:SendEmail (and the raw/templated variants) scoped to the identity, the account’s templates, and the configuration set.

For an email-address identity, FromEmailAddress defaults to the identity itself. For a domain identity, pass an address at the domain explicitly.

Note: while the account is in the SES sandbox, both the sender identity must be verified and every recipient must be a verified identity or the SES mailbox simulator (e.g. success@simulator.amazonses.com).

Send a Simple Message

// init
const sendEmail = yield* SES.SendEmail(identity);
// runtime
const result = yield* sendEmail({
Destination: { ToAddresses: ["success@simulator.amazonses.com"] },
Content: {
Simple: {
Subject: { Data: "Hello" },
Body: { Text: { Data: "Hello from SES." } },
},
},
});
// result.MessageId

Send Through a Configuration Set

const sendEmail = yield* SES.SendEmail(identity, configSet);

Send a Templated Message

const result = yield* sendEmail({
Destination: { ToAddresses: ["customer@example.com"] },
Content: {
Template: {
TemplateName: "welcome-template",
TemplateData: JSON.stringify({ name: "Ada" }),
},
},
});

Source: src/AWS/SES/Tenant.ts

An Amazon SES v2 tenant — a logical container that groups related SES resources (email identities, configuration sets, templates) together, each with its own reputation metrics, sending status, and optional tenant-scoped suppression list. Useful for isolating email sending across customers or business units within a single SES account.

Associate resources with a tenant using SES.TenantResourceAssociation. Deleting the tenant removes its resource associations but leaves the underlying resources in place.

Basic Tenant

import * as SES from "alchemy/AWS/SES";
const tenant = yield* SES.Tenant("CustomerA", {});

Tenant with a Scoped Suppression List

// SES requires the reasons and the scope together, so they travel as one
// prop rather than two independently-optional ones.
const tenant = yield* SES.Tenant("CustomerA", {
suppression: { reasons: ["BOUNCE", "COMPLAINT"], scope: "TENANT" },
});

Tenant with Tags

const tenant = yield* SES.Tenant("CustomerA", {
tags: { Customer: "acme", CostCenter: "growth" },
});
const tenant = yield* SES.Tenant("CustomerA", {});
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "mail.acme.example.com",
});
const configSet = yield* SES.ConfigurationSet("AcmeTracking", {});
// A resource must be associated before the tenant can send with it.
yield* SES.TenantResourceAssociation("AcmeIdentity", {
tenantName: tenant.tenantName,
resourceArn: identity.identityArn,
});
yield* SES.TenantResourceAssociation("AcmeConfigSet", {
tenantName: tenant.tenantName,
resourceArn: configSet.configurationSetArn,
});
// With scope "TENANT" the list is separate from the account's.
const tenant = yield* SES.Tenant("CustomerA", {
suppression: { reasons: ["BOUNCE", "COMPLAINT"], scope: "TENANT" },
});
// init — account-level bindings, scoped per call via TenantName
const suppress = yield* SES.PutSuppressedDestination();
const listSuppressed = yield* SES.ListSuppressedDestinations();
// runtime
yield* suppress({
EmailAddress: "hard-bounce@example.com",
Reason: "BOUNCE",
TenantName: yield* tenant.tenantName,
});
const { SuppressedDestinationSummaries } = yield* listSuppressed({
TenantName: yield* tenant.tenantName,
});

Source: src/AWS/SES/TenantResourceAssociation.ts

An association between an Amazon SES v2 tenant and a resource — an email identity, configuration set, or email template. Once associated, the resource can be used when sending email on behalf of the tenant. A single resource can be associated with multiple tenants.

This is an existence-only link with no mutable properties: changing either the tenant or the resource replaces the association.

TenantResourceAssociation: Associating Resources

Section titled “TenantResourceAssociation: Associating Resources”

Associate an Email Identity with a Tenant

import * as SES from "alchemy/AWS/SES";
const tenant = yield* SES.Tenant("CustomerA", {});
const identity = yield* SES.EmailIdentity("Sender", {
emailIdentity: "sender@example.com",
});
const association = yield* SES.TenantResourceAssociation("SenderLink", {
tenantName: tenant.tenantName,
resourceArn: identity.identityArn,
});

Associate a Configuration Set

const configSet = yield* SES.ConfigurationSet("AcmeTracking", {});
yield* SES.TenantResourceAssociation("ConfigSetLink", {
tenantName: tenant.tenantName,
resourceArn: configSet.configurationSetArn,
});

Associate an Email Template

const template = yield* SES.EmailTemplate("Welcome", {
subject: "Welcome, {{name}}!",
text: "Thanks for signing up, {{name}}.",
});
yield* SES.TenantResourceAssociation("TemplateLink", {
tenantName: tenant.tenantName,
resourceArn: template.templateArn,
});

Share One Identity Across Two Tenants

// A resource can belong to any number of tenants.
const acme = yield* SES.Tenant("Acme", {});
const globex = yield* SES.Tenant("Globex", {});
yield* SES.TenantResourceAssociation("AcmeSender", {
tenantName: acme.tenantName,
resourceArn: identity.identityArn,
});
yield* SES.TenantResourceAssociation("GlobexSender", {
tenantName: globex.tenantName,
resourceArn: identity.identityArn,
});