Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Oct 21, 2024. It is now read-only.

Commit ce169d8

Browse files
author
Frank
committed
Email: support event destinations
1 parent 9be2840 commit ce169d8

File tree

5 files changed

+219
-5
lines changed

5 files changed

+219
-5
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Resource } from "sst";
2+
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
3+
4+
const client = new SESv2Client();
5+
6+
export const sender = async () => {
7+
await client.send(
8+
new SendEmailCommand({
9+
FromEmailAddress: Resource.MyEmail.sender,
10+
Destination: {
11+
ToAddresses: [Resource.MyEmail.sender],
12+
},
13+
Content: {
14+
Simple: {
15+
Subject: {
16+
Data: "Hello World!",
17+
},
18+
Body: {
19+
Text: {
20+
Data: "Sent from my SST app.",
21+
},
22+
},
23+
},
24+
},
25+
})
26+
);
27+
28+
return {
29+
statusCode: 200,
30+
body: "Sent!",
31+
};
32+
};
33+
34+
export const notification = async (event: any) => {
35+
console.log(JSON.stringify(event, null, 2));
36+
return {
37+
statusCode: 200,
38+
body: "Received!",
39+
};
40+
};

examples/internal/playground/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"author": "",
1111
"license": "ISC",
1212
"dependencies": {
13-
"sst": "3.0.1-37"
13+
"@aws-sdk/client-sesv2": "^3.515.0",
14+
"sst": "3.2.31"
1415
}
1516
}

examples/internal/playground/sst.config.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default $config({
1313

1414
const vpc = addVpc();
1515
const bucket = addBucket();
16+
//const email = addEmail();
1617
//const apiv1 = addApiV1();
1718
//const apiv2 = addApiV2();
1819
//const app = addFunction();
@@ -32,6 +33,33 @@ export default $config({
3233
return bucket;
3334
}
3435

36+
function addEmail() {
37+
const topic = new sst.aws.SnsTopic("MyTopic");
38+
topic.subscribe("functions/email/index.notification");
39+
40+
const email = new sst.aws.Email("MyEmail", {
41+
sender: "[email protected]",
42+
events: [
43+
{
44+
name: "notif",
45+
types: ["delivery"],
46+
topic: topic.arn,
47+
},
48+
],
49+
});
50+
51+
const sender = new sst.aws.Function("MyApi", {
52+
handler: "functions/email/index.sender",
53+
link: [email],
54+
url: true,
55+
});
56+
57+
ret.emailSend = sender.url;
58+
ret.email = email.sender;
59+
ret.emailConfig = email.configSet;
60+
return ret;
61+
}
62+
3563
function addCron() {
3664
const cron = new sst.aws.Cron("MyCron", {
3765
schedule: "rate(1 minute)",

platform/src/components/aws/email.ts

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
99
import { Link } from "../link";
1010
import { Input } from "../input";
1111
import { Dns } from "../dns";
1212
import { dns as awsDns } from "./dns.js";
1313
import { ses, sesv2 } from "@pulumi/aws";
1414
import { 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

1651
export 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

120180
interface EmailRef {
121181
ref: boolean;
122182
identity: sesv2.EmailIdentity;
183+
configurationSet: sesv2.ConfigurationSet;
123184
}
124185

125186
/**
@@ -204,6 +265,7 @@ interface EmailRef {
204265
export 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

platform/src/components/component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export class Component extends ComponentResource {
107107
"aws:s3/bucketV2:BucketV2",
108108
"aws:servicediscovery/privateDnsNamespace:PrivateDnsNamespace",
109109
"aws:servicediscovery/service:Service",
110+
"aws:sesv2/configurationSet:ConfigurationSet",
110111
].includes(args.type) ||
111112
// resources not prefixed
112113
[
@@ -163,6 +164,7 @@ export class Component extends ComponentResource {
163164
"aws:s3/bucketWebsiteConfigurationV2:BucketWebsiteConfigurationV2",
164165
"aws:secretsmanager/secretVersion:SecretVersion",
165166
"aws:ses/domainIdentityVerification:DomainIdentityVerification",
167+
"aws:sesv2/configurationSetEventDestination:ConfigurationSetEventDestination",
166168
"aws:sesv2/emailIdentity:EmailIdentity",
167169
"aws:sns/topicSubscription:TopicSubscription",
168170
"cloudflare:index/record:Record",

0 commit comments

Comments
 (0)