1import * as sesv2 from "@distilled.cloud/aws/sesv2";
2import * as Effect from "effect/Effect";
3import * as Provider from "../../Provider.ts";
4import { Resource } from "../../Resource.ts";
5import type { Providers } from "../Providers.ts";
6import type { SuppressionListReason } from "./ConfigurationSet.ts";
7
8/**
9 * Whether an SES account feature is `ENABLED` or `DISABLED`.
10 */
11export type FeatureStatus = sesv2.FeatureStatus;
12
13export interface AccountVdmSettings {
14 /**
15 * Whether Virtual Deliverability Manager (VDM) is enabled for the account.
16 */
17 enabled: FeatureStatus;
18 /**
19 * Whether the VDM dashboard's per-message engagement tracking (opens and
20 * clicks) is enabled.
21 */
22 dashboardEngagementMetrics?: FeatureStatus;
23 /**
24 * Whether VDM Guardian's optimized shared delivery is enabled.
25 */
26 guardianOptimizedSharedDelivery?: FeatureStatus;
27}
28
29export interface AccountSuppressionSettings {
30 /**
31 * The bounce/complaint reasons for which SES adds destinations to the
32 * account-level suppression list.
33 *
34 * Required: SES treats `putAccountSuppressionAttributes` with no reasons as
35 * "suppress nothing", so an optional member would let `suppression: {}`
36 * silently clear the account-wide list.
37 */
38 reasons: SuppressionListReason[];
39}
40
41export interface AccountSettingsProps {
42 /**
43 * Whether email sending is enabled for the whole account.
44 *
45 * :::caution
46 * Setting this to `false` **stops all sending for the entire account**, not
47 * just this stack's mail. Only manage it when you intend to pause account-wide
48 * sending.
49 * :::
50 */
51 sendingEnabled?: boolean;
52 /**
53 * Account-level suppression list configuration, applied via
54 * `putAccountSuppressionAttributes`.
55 */
56 suppression?: AccountSuppressionSettings;
57 /**
58 * Virtual Deliverability Manager configuration, applied via
59 * `putAccountVdmAttributes`.
60 */
61 vdm?: AccountVdmSettings;
62}
63
64export interface AccountSettings extends Resource<
65 "AWS.SES.AccountSettings",
66 AccountSettingsProps,
67 {
68 /** Whether email sending is enabled for the account. */
69 sendingEnabled: boolean;
70 /** Whether VDM is enabled for the account. */
71 vdmEnabled: FeatureStatus;
72 /** The account-level suppression reasons currently configured. */
73 suppressedReasons: SuppressionListReason[];
74 },
75 never,
76 Providers
77> {}
78
79/**
80 * Account-level Amazon SES v2 settings — an account/region singleton that
81 * manages account-wide sending status, the account suppression list, and
82 * Virtual Deliverability Manager (VDM) configuration.
83 *
84 * Only the aspects you specify are managed: omit `sendingEnabled`, `suppression`,
85 * or `vdm` to leave that setting untouched.
86 *
87 * Deleting this resource is a **no-op** — it leaves the account settings exactly
88 * as they are. Unlike a normal resource there is nothing to tear down: these are
89 * account-global toggles with no single safe default, and resetting them (e.g.
90 * disabling sending or clearing the suppression list) would affect live mail
91 * beyond this stack. Change the props and re-deploy to adjust them.
92 * ### Configuring the Account
93 * **Example:** Enable VDM with Engagement Tracking
94 * ```typescript
95 * import * as SES from "alchemy/AWS/SES";
96 *
97 * const settings = yield* SES.AccountSettings("Account", {
98 * vdm: {
99 * enabled: "ENABLED",
100 * dashboardEngagementMetrics: "ENABLED",
101 * },
102 * });
103 * ```
104 *
105 * **Example:** Configure the Suppression List
106 * ```typescript
107 * const settings = yield* SES.AccountSettings("Account", {
108 * suppression: { reasons: ["BOUNCE", "COMPLAINT"] },
109 * });
110 * ```
111 *
112 * **Example:** Enable Guardian Optimized Shared Delivery
113 * ```typescript
114 * const settings = yield* SES.AccountSettings("Account", {
115 * vdm: {
116 * enabled: "ENABLED",
117 * guardianOptimizedSharedDelivery: "ENABLED",
118 * },
119 * });
120 * ```
121 *
122 * ### Pausing Account-Wide Sending
123 * **Example:** Stop All Sending for the Account
124 * ```typescript
125 * // WARNING: this halts every outbound email in the account, including mail
126 * // sent by stacks and systems outside this one. Prefer a configuration
127 * // set's sendingEnabled to pause a single sending path.
128 * const settings = yield* SES.AccountSettings("Account", {
129 * sendingEnabled: false,
130 * });
131 * ```
132 *
133 * **Example:** Manage Only VDM and Leave Sending Alone
134 * ```typescript
135 * // Omitted aspects are never touched — this deploy will not read or write
136 * // the account's sending status or suppression list.
137 * const settings = yield* SES.AccountSettings("Account", {
138 * vdm: { enabled: "ENABLED" },
139 * });
140 * ```
141 *
142 * @resource
143 */
144export const AccountSettings = Resource<AccountSettings>(
145 "AWS.SES.AccountSettings",
146);
147
148const sameReasons = (
149 a: ReadonlyArray<SuppressionListReason> | undefined,
150 b: ReadonlyArray<SuppressionListReason> | undefined,
151): boolean => {
152 const key = (reasons: ReadonlyArray<SuppressionListReason> | undefined) =>
153 JSON.stringify([...(reasons ?? [])].sort());
154 return key(a) === key(b);
155};
156
157export const AccountSettingsProvider = () =>
158 Provider.effect(
159 AccountSettings,
160 Effect.gen(function* () {
161 const observe = sesv2.getAccount({}).pipe(
162 Effect.map((response) => ({
163 sendingEnabled: response.SendingEnabled ?? false,
164 vdmEnabled: response.VdmAttributes?.VdmEnabled ?? "DISABLED",
165 // distilled types SuppressedReasons open (`| (string & {})`) so the
166 // SDK survives new SES reasons. Narrow to the closed union the props
167 // use, dropping anything we do not model, so attributes assign back
168 // into props.
169 suppressedReasons: (
170 response.SuppressionAttributes?.SuppressedReasons ?? []
171 ).filter(
172 (reason): reason is SuppressionListReason =>
173 reason === "BOUNCE" || reason === "COMPLAINT",
174 ),
175 dashboardEngagementMetrics:
176 response.VdmAttributes?.DashboardAttributes?.EngagementMetrics,
177 guardianOptimizedSharedDelivery:
178 response.VdmAttributes?.GuardianAttributes?.OptimizedSharedDelivery,
179 })),
180 );
181
182 const toAttrs = (observed: {
183 sendingEnabled: boolean;
184 vdmEnabled: FeatureStatus;
185 suppressedReasons: SuppressionListReason[];
186 }) => ({
187 sendingEnabled: observed.sendingEnabled,
188 vdmEnabled: observed.vdmEnabled,
189 suppressedReasons: observed.suppressedReasons,
190 });
191
192 return AccountSettings.Provider.of({
193 // Account-global singleton: nuke must not reset it.
194 nuke: { singleton: true },
195 stables: [],
196
197 // The account settings always exist.
198 list: Effect.fn(function* () {
199 return [toAttrs(yield* observe)];
200 }),
201
202 read: Effect.fn(function* () {
203 return toAttrs(yield* observe);
204 }),
205
206 reconcile: Effect.fn(function* ({ news, session }) {
207 // OBSERVE the live account settings; only call each put on a real
208 // delta for the aspects the caller actually manages.
209 let observed = yield* observe;
210
211 if (
212 news.sendingEnabled !== undefined &&
213 observed.sendingEnabled !== news.sendingEnabled
214 ) {
215 yield* sesv2.putAccountSendingAttributes({
216 SendingEnabled: news.sendingEnabled,
217 });
218 }
219
220 if (
221 news.suppression !== undefined &&
222 !sameReasons(observed.suppressedReasons, news.suppression.reasons)
223 ) {
224 yield* sesv2.putAccountSuppressionAttributes({
225 SuppressedReasons: news.suppression.reasons,
226 });
227 }
228
229 if (
230 news.vdm !== undefined &&
231 (observed.vdmEnabled !== news.vdm.enabled ||
232 observed.dashboardEngagementMetrics !==
233 news.vdm.dashboardEngagementMetrics ||
234 observed.guardianOptimizedSharedDelivery !==
235 news.vdm.guardianOptimizedSharedDelivery)
236 ) {
237 yield* sesv2.putAccountVdmAttributes({
238 VdmAttributes: {
239 VdmEnabled: news.vdm.enabled,
240 DashboardAttributes:
241 news.vdm.dashboardEngagementMetrics !== undefined
242 ? { EngagementMetrics: news.vdm.dashboardEngagementMetrics }
243 : undefined,
244 GuardianAttributes:
245 news.vdm.guardianOptimizedSharedDelivery !== undefined
246 ? {
247 OptimizedSharedDelivery:
248 news.vdm.guardianOptimizedSharedDelivery,
249 }
250 : undefined,
251 },
252 });
253 }
254
255 // Re-read so the returned attributes reflect the converged state.
256 observed = yield* observe;
257 yield* session.note(
258 `sending=${observed.sendingEnabled} vdm=${observed.vdmEnabled}`,
259 );
260 return toAttrs(observed);
261 }),
262
263 // Account-global singleton: deleting only stops managing the settings.
264 // We deliberately leave them in place — there is no single safe default
265 // to reset to, and changing them would affect account-wide mail.
266 delete: Effect.fn(function* () {}),
267 });
268 }),
269 );