@@ -5,13 +5,48 @@ import {
55 interpolate ,
66 output ,
77} from "@pulumi/pulumi" ;
8- import { Component , Transform , transform } from "../component" ;
8+ import { Component , Prettify , Transform , transform } from "../component" ;
99import { Link } from "../link" ;
1010import { Input } from "../input" ;
1111import { Dns } from "../dns" ;
1212import { dns as awsDns } from "./dns.js" ;
1313import { ses , sesv2 } from "@pulumi/aws" ;
1414import { permission } from "./permission" ;
15+ import { RandomId } from "@pulumi/random" ;
16+ import { hashNumberToPrettyString , physicalName } from "../naming" ;
17+ import { VisibleError } from "../error" ;
18+
19+ interface Events {
20+ /**
21+ * The name of the event.
22+ */
23+ name : Input < string > ;
24+ /**
25+ * The types of events to send.
26+ */
27+ types : Input <
28+ Input <
29+ | "send"
30+ | "reject"
31+ | "bounce"
32+ | "complaint"
33+ | "delivery"
34+ | "delivery-delay"
35+ | "rendering-failure"
36+ | "subscription"
37+ | "open"
38+ | "click"
39+ > [ ]
40+ > ;
41+ /**
42+ * The ARN of the SNS topic to send events to.
43+ */
44+ topic ?: Input < string > ;
45+ /**
46+ * The ARN of the EventBridge bus to send events to.
47+ */
48+ bus ?: Input < string > ;
49+ }
1550
1651export interface EmailArgs {
1752 /**
@@ -105,6 +140,27 @@ export interface EmailArgs {
105140 * ```
106141 */
107142 dmarc ?: Input < string > ;
143+ /**
144+ * Configure event notifications for this Email component.
145+ *
146+ * :::tip
147+ * You don't need to use a Lambda layer to use FFmpeg.
148+ * :::
149+ *
150+ * @default No event notifications
151+ * @example
152+ *
153+ * ```js
154+ * {
155+ * events: {
156+ * name: "OnBounce",
157+ * types: ["bounce"],
158+ * topic: "arn:aws:sns:us-east-1:123456789012:MyTopic"
159+ * }
160+ * }
161+ * ```
162+ */
163+ events ?: Input < Prettify < Events > [ ] > ;
108164 /**
109165 * [Transform](/docs/components#transform) how this component creates its underlying
110166 * resources.
@@ -114,12 +170,17 @@ export interface EmailArgs {
114170 * Transform the SES identity resource.
115171 */
116172 identity ?: Transform < sesv2 . EmailIdentityArgs > ;
173+ /**
174+ * Transform the SES configuration set resource.
175+ */
176+ configurationSet ?: Transform < sesv2 . ConfigurationSetArgs > ;
117177 } ;
118178}
119179
120180interface EmailRef {
121181 ref : boolean ;
122182 identity : sesv2 . EmailIdentity ;
183+ configurationSet : sesv2 . ConfigurationSet ;
123184}
124185
125186/**
@@ -204,6 +265,7 @@ interface EmailRef {
204265export class Email extends Component implements Link . Linkable {
205266 private _sender : Output < string > ;
206267 private identity : sesv2 . EmailIdentity ;
268+ private configurationSet : sesv2 . ConfigurationSet ;
207269
208270 constructor ( name : string , args : EmailArgs , opts ?: ComponentResourceOptions ) {
209271 super ( __pulumiType , name , args , opts ) ;
@@ -212,14 +274,17 @@ export class Email extends Component implements Link.Linkable {
212274 const ref = args as unknown as EmailRef ;
213275 this . _sender = ref . identity . emailIdentity ;
214276 this . identity = ref . identity ;
277+ this . configurationSet = ref . configurationSet ;
215278 return ;
216279 }
217280
218281 const parent = this ;
219282 const isDomain = checkIsDomain ( ) ;
220283 const dns = normalizeDns ( ) ;
221284 const dmarc = normalizeDmarc ( ) ;
285+ const configurationSet = createConfigurationSet ( ) ;
222286 const identity = createIdentity ( ) ;
287+ createEvents ( ) ;
223288 isDomain . apply ( ( isDomain ) => {
224289 if ( ! isDomain ) return ;
225290 createDkimRecords ( ) ;
@@ -229,6 +294,7 @@ export class Email extends Component implements Link.Linkable {
229294
230295 this . _sender = output ( args . sender ) ;
231296 this . identity = identity ;
297+ this . configurationSet = configurationSet ;
232298
233299 function checkIsDomain ( ) {
234300 return output ( args . sender ) . apply ( ( sender ) => ! sender . includes ( "@" ) ) ;
@@ -256,17 +322,73 @@ export class Email extends Component implements Link.Linkable {
256322 return args . dmarc ?? `v=DMARC1; p=none;` ;
257323 }
258324
325+ function createConfigurationSet ( ) {
326+ const transformed = transform (
327+ args . transform ?. configurationSet ,
328+ `${ name } Config` ,
329+ { } as sesv2 . ConfigurationSetArgs ,
330+ { parent } ,
331+ ) ;
332+
333+ if ( ! transformed [ 1 ] . configurationSetName ) {
334+ const randomId = new RandomId (
335+ `${ name } Id` ,
336+ { byteLength : 6 } ,
337+ { parent } ,
338+ ) ;
339+ transformed [ 1 ] . configurationSetName = randomId . dec . apply ( ( dec ) =>
340+ physicalName (
341+ 64 ,
342+ name ,
343+ `-${ hashNumberToPrettyString ( parseInt ( dec ) , 8 ) } ` ,
344+ ) . toLowerCase ( ) ,
345+ ) ;
346+ }
347+
348+ return new sesv2 . ConfigurationSet ( ...transformed ) ;
349+ }
350+
259351 function createIdentity ( ) {
260352 return new sesv2 . EmailIdentity (
261353 ...transform (
262354 args . transform ?. identity ,
263355 `${ name } Identity` ,
264- { emailIdentity : args . sender } ,
356+ {
357+ emailIdentity : args . sender ,
358+ configurationSetName : configurationSet . configurationSetName ,
359+ } ,
265360 { parent } ,
266361 ) ,
267362 ) ;
268363 }
269364
365+ function createEvents ( ) {
366+ output ( args . events ?? [ ] ) . apply ( ( events ) =>
367+ events . forEach ( ( event ) => {
368+ new sesv2 . ConfigurationSetEventDestination (
369+ `${ name } Event${ event . name } ` ,
370+ {
371+ configurationSetName : configurationSet . configurationSetName ,
372+ eventDestinationName : event . name ,
373+ eventDestination : {
374+ matchingEventTypes : event . types . map ( ( t ) =>
375+ t . toUpperCase ( ) . replaceAll ( "-" , "_" ) ,
376+ ) ,
377+ ...( event . bus
378+ ? { eventBridgeDestination : { eventBusArn : event . bus } }
379+ : { } ) ,
380+ ...( event . topic
381+ ? { snsDestination : { topicArn : event . topic } }
382+ : { } ) ,
383+ enabled : true ,
384+ } ,
385+ } ,
386+ { parent } ,
387+ ) ;
388+ } ) ,
389+ ) ;
390+ }
391+
270392 function createDkimRecords ( ) {
271393 all ( [ dns , identity ?. dkimSigningAttributes . tokens ] ) . apply (
272394 ( [ dns , tokens ] ) => {
@@ -321,6 +443,13 @@ export class Email extends Component implements Link.Linkable {
321443 return this . _sender ;
322444 }
323445
446+ /**
447+ * The name of the configuration set.
448+ */
449+ public get configSet ( ) {
450+ return this . configurationSet . configurationSetName ;
451+ }
452+
324453 /**
325454 * The underlying [resources](/docs/components/#nodes) this component creates.
326455 */
@@ -330,6 +459,10 @@ export class Email extends Component implements Link.Linkable {
330459 * The Amazon SES identity.
331460 */
332461 identity : this . identity ,
462+ /**
463+ * The Amazon SES configuration set.
464+ */
465+ configurationSet : this . configurationSet ,
333466 } ;
334467 }
335468
@@ -338,11 +471,12 @@ export class Email extends Component implements Link.Linkable {
338471 return {
339472 properties : {
340473 sender : this . _sender ,
474+ configSet : this . configSet ,
341475 } ,
342476 include : [
343477 permission ( {
344478 actions : [ "ses:*" ] ,
345- resources : [ this . identity . arn ] ,
479+ resources : [ this . identity . arn , this . configurationSet . arn ] ,
346480 } ) ,
347481 ] ,
348482 } ;
@@ -370,7 +504,16 @@ export class Email extends Component implements Link.Linkable {
370504 */
371505 public static get ( name : string , sender : Input < string > ) {
372506 const identity = sesv2 . EmailIdentity . get ( `${ name } Identity` , sender ) ;
373- return new Email ( name , { ref : true , identity } as unknown as EmailArgs ) ;
507+ const configSet = sesv2 . ConfigurationSet . get (
508+ `${ name } Config` ,
509+ identity . configurationSetName . apply ( ( v ) => v ! ) ,
510+ ) ;
511+
512+ return new Email ( name , {
513+ ref : true ,
514+ identity,
515+ configurationSet : configSet ,
516+ } as unknown as EmailArgs ) ;
374517 }
375518}
376519
0 commit comments