diff --git a/examples/aws-jsx-email/.gitignore b/examples/aws-jsx-email/.gitignore new file mode 100644 index 000000000..fccbe3fea --- /dev/null +++ b/examples/aws-jsx-email/.gitignore @@ -0,0 +1,15 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +node_modules + +# env +.env + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# sst +.sst diff --git a/examples/aws-jsx-email/README.md b/examples/aws-jsx-email/README.md new file mode 100644 index 000000000..a41fc0ef7 --- /dev/null +++ b/examples/aws-jsx-email/README.md @@ -0,0 +1 @@ +# aws-jsx-email diff --git a/examples/aws-jsx-email/index.ts b/examples/aws-jsx-email/index.ts new file mode 100644 index 000000000..907afeca8 --- /dev/null +++ b/examples/aws-jsx-email/index.ts @@ -0,0 +1,37 @@ +import { Resource } from "sst"; +import { render } from "jsx-email"; +import { Template } from "./templates/email"; +import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2"; + +const client = new SESv2Client(); + +export const handler = async () => { + await client.send( + new SendEmailCommand({ + FromEmailAddress: Resource.MyEmail.sender, + Destination: { + ToAddresses: [Resource.MyEmail.sender], + }, + Content: { + Simple: { + Subject: { + Data: "Hello World!", + }, + Body: { + Html: { + Data: await render(Template({ + email: "spongebob@example.com", + name: "Spongebob Squarepants" + })), + }, + }, + }, + }, + }) + ); + + return { + statusCode: 200, + body: "Sent!" + }; +}; diff --git a/examples/aws-jsx-email/package.json b/examples/aws-jsx-email/package.json new file mode 100644 index 000000000..04b5e2995 --- /dev/null +++ b/examples/aws-jsx-email/package.json @@ -0,0 +1,22 @@ +{ + "name": "aws-jsx-email", + "version": "0.0.0", + "private": true, + "description": "A simple starter for jsx-email", + "scripts": { + "build": "email build ./templates", + "create": "email create", + "dev": "email preview ./templates" + }, + "dependencies": { + "@aws-sdk/client-sesv2": "^3.651.1", + "jsx-email": "^1.10.11", + "sst": "latest" + }, + "devDependencies": { + "@types/aws-lambda": "8.10.145", + "@types/react": "^18.2.0", + "react": "^18.2.0", + "typescript": "^5.2.2" + } +} diff --git a/examples/aws-jsx-email/sst-env.d.ts b/examples/aws-jsx-email/sst-env.d.ts new file mode 100644 index 000000000..04d80476f --- /dev/null +++ b/examples/aws-jsx-email/sst-env.d.ts @@ -0,0 +1,18 @@ +/* This file is auto-generated by SST. Do not edit. */ +/* tslint:disable */ +/* eslint-disable */ +import "sst" +export {} +declare module "sst" { + export interface Resource { + "MyApi": { + "name": string + "type": "sst.aws.Function" + "url": string + } + "MyEmail": { + "sender": string + "type": "sst.aws.Email" + } + } +} diff --git a/examples/aws-jsx-email/sst.config.ts b/examples/aws-jsx-email/sst.config.ts new file mode 100644 index 000000000..a17fd32f9 --- /dev/null +++ b/examples/aws-jsx-email/sst.config.ts @@ -0,0 +1,63 @@ +/// + +/** + * ## AWS JSX Email + * + * Uses [JSX Email](https://jsx.email) and the `Email` component to design and send emails. + * + * To test this example, change the `sst.config.ts` to use your own email address. + * + * ```ts title="sst.config.ts" + * sender: "email@example.com" + * ``` + * + * Then run. + * + * ```bash + * npm install + * npx sst dev + * ``` + * + * You'll get an email from AWS asking you to confirm your email address. Click the link to + * verify it. + * + * Next, go to the URL in the `sst dev` CLI output. You should now receive an email rendered + * using JSX Email. + * + * ```ts title="index.ts" + * import { Template } from "./templates/email"; + * + * await render(Template({ + * email: "spongebob@example.com", + * name: "Spongebob Squarepants" + * })) + * ``` + * + * Once you are ready to go to production, you can: + * + * - [Request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html) for SES + * - And [use your domain](/docs/component/aws/email/) to send emails + */ +export default $config({ + app(input) { + return { + name: "aws-jsx-email", + removal: input?.stage === "production" ? "retain" : "remove", + home: "aws", + }; + }, + async run() { + const email = new sst.aws.Email("MyEmail", { + sender: "email@example.com", + }); + const api = new sst.aws.Function("MyApi", { + handler: "index.handler", + link: [email], + url: true, + }); + + return { + api: api.url, + }; + }, +}); diff --git a/examples/aws-jsx-email/templates/email.tsx b/examples/aws-jsx-email/templates/email.tsx new file mode 100644 index 000000000..4c4591a9e --- /dev/null +++ b/examples/aws-jsx-email/templates/email.tsx @@ -0,0 +1,96 @@ +import { + Body, + Button, + Container, + Head, + Hr, + Html, + Link, + Preview, + Section, + Text +} from 'jsx-email'; + + +export type TemplateProps = { + email: string; + name: string; +} + +const main = { + backgroundColor: '#f6f9fc', + fontFamily: + '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif' +}; + +const container = { + backgroundColor: '#ffffff', + margin: '0 auto', + marginBottom: '64px', + padding: '20px 0 48px' +}; + +const box = { + padding: '0 48px' +}; + +const hr = { + borderColor: '#e6ebf1', + margin: '20px 0' +}; + +const paragraph = { + color: '#777', + fontSize: '16px', + lineHeight: '24px', + textAlign: 'left' as const +}; + +const anchor = { + color: '#777' +}; + +const button = { + backgroundColor: 'coral', + borderRadius: '5px', + color: '#fff', + display: 'block', + fontSize: '16px', + fontWeight: 'bold', + textAlign: 'center' as const, + textDecoration: 'none', + width: '100%', + padding: '10px' +}; + +export const defaultProps = { + email: 'batman@example.com', + name: 'Bruce Wayne' +} as TemplateProps; + +export const templateName = 'aws-jsx-email'; + +export const Template = ({ email, name }: TemplateProps) => ( + + + This is our email preview text for {name} <{email}> + + +
+ This is our email body text + +
+ + This is text content with a{' '} + + link + + . + +
+
+ + +); diff --git a/examples/aws-jsx-email/tsconfig.json b/examples/aws-jsx-email/tsconfig.json new file mode 100644 index 000000000..b49214f86 --- /dev/null +++ b/examples/aws-jsx-email/tsconfig.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "lib": ["ES2023"], + "module": "ESNext", + "moduleResolution": "node", + "noEmitOnError": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "target": "ESNext" + }, + "exclude": ["**/dist", "**/node_modules", "sst.config.ts"], + "include": ["templates", "index.ts", "sst-env.d.ts"] +} diff --git a/www/src/content/docs/docs/examples.mdx b/www/src/content/docs/docs/examples.mdx index 15b45e523..07c8d5594 100644 --- a/www/src/content/docs/docs/examples.mdx +++ b/www/src/content/docs/docs/examples.mdx @@ -342,6 +342,61 @@ return { View the [full example](https://github.com/sst/ion/tree/dev/examples/aws-info). +--- +## AWS JSX Email + +Uses [JSX Email](https://jsx.email) and the `Email` component to design and send emails. + +To test this example, change the `sst.config.ts` to use your own email address. + +```ts title="sst.config.ts" +sender: "email@example.com" +``` + +Then run. + +```bash +npm install +npx sst dev +``` + +You'll get an email from AWS asking you to confirm your email address. Click the link to +verify it. + +Next, go to the URL in the `sst dev` CLI output. You should now receive an email rendered +using JSX Email. + +```ts title="index.ts" +import { Template } from "./templates/email"; + +await render(Template({ + email: "spongebob@example.com", + name: "Spongebob Squarepants" +})) +``` + +Once you are ready to go to production, you can: + +- [Request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html) for SES +- And [use your domain](/docs/component/aws/email/) to send emails +```ts title="sst.config.ts" +const email = new sst.aws.Email("MyEmail", { + sender: "email@example.com", +}); +const api = new sst.aws.Function("MyApi", { + handler: "index.handler", + link: [email], + url: true, +}); + +return { + api: api.url, +}; +``` + +View the [full example](https://github.com/sst/ion/tree/dev/examples/aws-jsx-email). + + --- ## Kinesis streams @@ -465,7 +520,7 @@ the [`--fallback`](/docs/reference/cli#secret) flag. ```ts title="sst.config.ts" const username = new sst.Secret("USERNAME"); const password = new sst.Secret("PASSWORD"); -const basicAuth = $output([username.value, password.value]).apply( +const basicAuth = $resolve([username.value, password.value]).apply( ([username, password]) => Buffer.from(`${username}:${password}`).toString("base64") ); @@ -711,6 +766,43 @@ return { View the [full example](https://github.com/sst/ion/tree/dev/examples/aws-queue). +--- +## AWS Router and bucket + +Creates a router that serves static files from the `public` folder in a bucket. +```ts title="sst.config.ts" +// Create a bucket that CloudFront can access +const bucket = new sst.aws.Bucket("MyBucket", { + access: "cloudfront", +}); + +// Upload the image to the `public` folder +new aws.s3.BucketObjectv2("MyImage", { + bucket: bucket.name, + key: "public/spongebob.svg", + contentType: "image/svg+xml", + source: new $util.asset.FileAsset( + path.join($cli.paths.root, "spongebob.svg") + ), +}); + +const router = new sst.aws.Router("MyRouter", { + routes: { + "/*": { + bucket, + rewrite: { regex: "^/(.*)$", to: "/public/$1" }, + }, + }, +}); + +return { + image: $interpolate`${router.url}/spongebob.svg`, +}; +``` + +View the [full example](https://github.com/sst/ion/tree/dev/examples/aws-router-bucket). + + --- ## Router and function URL @@ -827,7 +919,7 @@ the [`--fallback`](/docs/reference/cli#secret) flag. ```ts title="sst.config.ts" const username = new sst.Secret("USERNAME"); const password = new sst.Secret("PASSWORD"); -const basicAuth = $output([username.value, password.value]).apply( +const basicAuth = $resolve([username.value, password.value]).apply( ([username, password]) => Buffer.from(`${username}:${password}`).toString("base64") ); @@ -837,20 +929,20 @@ new sst.aws.StaticSite("MySite", { // Don't password protect prod edge: $app.stage !== "production" ? { - viewerRequest: { - injection: $interpolate` - if ( - !event.request.headers.authorization - || event.request.headers.authorization.value !== "Basic ${basicAuth}" - ) { - return { - statusCode: 401, - headers: { - "www-authenticate": { value: "Basic" } - } - }; - }`, - }, + viewerRequest: { + injection: $interpolate` + if ( + !event.request.headers.authorization + || event.request.headers.authorization.value !== "Basic ${basicAuth}" + ) { + return { + statusCode: 401, + headers: { + "www-authenticate": { value: "Basic" } + } + }; + }`, + }, } : undefined, });