Skip to content

Commit

Permalink
email
Browse files Browse the repository at this point in the history
  • Loading branch information
thdxr committed Jun 30, 2024
1 parent 8122f84 commit 1b53851
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 1 deletion.
Binary file modified bun.lockb
Binary file not shown.
9 changes: 8 additions & 1 deletion infra/bus.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { database } from "./database";
import { email } from "./email";
import { allSecrets } from "./secret";

export const bus = new sst.aws.Bus("Bus");

bus.subscribe({
handler: "./packages/functions/src/event/event.handler",
link: [database, ...allSecrets],
link: [database, email, ...allSecrets],
permissions: [
{
actions: ["ses:SendEmail"],
resources: ["*"],
},
],
});
16 changes: 16 additions & 0 deletions infra/short.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
if ($app.stage === "production") {
const zone = cloudflare.getZoneOutput({
name: "trm.sh",
});
new cloudflare.PageRule("ShortRedirect", {
zoneId: zone.id,
target: "trm.sh/*",
actions: {
forwardingUrl: {
url: "https://terminal.shop/$1",
statusCode: 301,
},
},
priority: 1,
});
}
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@types/luxon": "3.4.2"
},
"dependencies": {
"@aws-sdk/client-sesv2": "3.606.0",
"@terminal/email": "workspace:",
"@hono/valibot-validator": "0.2.2",
"@hono/zod-openapi": "0.11.0",
"@paralleldrive/cuid2": "2.2.2",
Expand Down
37 changes: 37 additions & 0 deletions packages/core/src/email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
import { Resource } from "sst";

export module Email {
const ses = new SESv2Client({});

export async function send(
from: string,
to: string,
subject: string,
body: string,
) {
from = from + "@" + Resource.Email.sender;
console.log("sending email", subject, from, to);
const params = {
Destination: {
ToAddresses: [to],
},
Content: {
Simple: {
Body: {
Html: {
Data: body,
},
},
Subject: {
Data: subject,
},
},
},
FromEmailAddress: from,
};

const command = new SendEmailCommand(params);
await ses.send(command);
}
}
53 changes: 53 additions & 0 deletions packages/core/src/email/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Sample } from "@terminal/email/templates/email";
import { useTransaction } from "@terminal/core/drizzle/transaction";
import { render as renderTemplate } from "jsx-email";
import { userTable } from "../user/user.sql";
import { orderTable } from "../order/order.sql";
import { and, count, eq, lte, sql } from "drizzle-orm";
import { Email } from "./index";
export module Template {
const templates = {
Sample,
};

export function render<Name extends keyof typeof templates>(
name: Name,
data: Parameters<(typeof templates)[Name]>[0],
) {
return renderTemplate(templates[name](data));
}

export async function sendOrderConfirmation(orderID: string) {
const order = await useTransaction((tx) =>
tx
.select({
email: userTable.email,
name: userTable.name,
index: sql<string>`${tx
.select({ index: count() })
.from(orderTable)
.where(
and(
eq(orderTable.userID, userTable.id),
lte(orderTable.id, orderID),
),
)}`,
})
.from(orderTable)
.innerJoin(userTable, eq(userTable.id, orderTable.userID))
.where(eq(orderTable.id, orderID))
.then((rows) => rows[0]),
);
if (!order) throw new Error(`Order not found: ${orderID}`);
const template = await render("Sample", {
email: order.email!,
name: order.name!,
});
await Email.send(
"order",
order.email!,
`Terminal Order #${order.index} Confirmation`,
template,
);
}
}
5 changes: 5 additions & 0 deletions packages/core/src/shippo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export module Shippo {
name: sql`CONCAT(${productTable.name}, " - ", ${productVariantTable.name})`,
quantity: orderItem.quantity,
amount: orderItem.amount,
trackingNumber: orderTable.trackingNumber,
})
.from(orderTable)
.innerJoin(orderItem, eq(orderItem.orderID, orderTable.id))
Expand All @@ -32,6 +33,10 @@ export module Shippo {
.execute(),
);
if (!items.length) throw new Error("Order not found");
if (items.some((item) => item.trackingNumber)) {
console.log("shipment already created", orderID);
return;
}
const shipping = items[0]!.address;
const weight = items.reduce((sum, item) => sum + item.quantity * 12, 0);
const order = await api("POST", "/orders", {
Expand Down
1 change: 1 addition & 0 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"module": "esnext",
"jsx": "react",
"moduleResolution": "bundler",
"noUncheckedIndexedAccess": true,
}
Expand Down
12 changes: 12 additions & 0 deletions packages/email/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 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*
1 change: 1 addition & 0 deletions packages/email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# email
19 changes: 19 additions & 0 deletions packages/email/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@terminal/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": {
"jsx-email": "^1.10.11"
},
"devDependencies": {
"react": "^18.2.0",
"@types/react": "^18.2.0",
"typescript": "^5.2.2"
}
}
85 changes: 85 additions & 0 deletions packages/email/templates/email.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
Body,
Button,
Container,
Head,
Hr,
Html,
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: "#777",
borderRadius: "5px",
color: "#fff",
display: "block",
fontSize: "16px",
fontWeight: "bold",
textAlign: "center" as const,
textDecoration: "none",
width: "100%",
padding: "10px",
};

export const defaultProps = {} as TemplateProps;

export const templateName = "email";

export const Sample = (_props: TemplateProps) => (
<Html>
<Head />
<Preview>Order Confirmed</Preview>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Text style={paragraph}>Order Confirmed</Text>
<Button style={button}>This button does nothing</Button>
<Hr style={hr} />
<Text style={paragraph}>
Look we're still working on this email template okay?
</Text>
</Section>
</Container>
</Body>
</Html>
);
24 changes: 24 additions & 0 deletions packages/email/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"],
"include": ["templates"]
}
7 changes: 7 additions & 0 deletions packages/functions/src/event/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { Order } from "@terminal/core/order/order";
import { Shippo } from "@terminal/core/shippo/index";
import { User } from "@terminal/core/user/index";
import { Stripe } from "@terminal/core/stripe";
import { Template } from "@terminal/core/email/template";
import { Email } from "@terminal/core/email/index";
import { useTransaction } from "@terminal/core/drizzle/transaction";
import { userTable } from "@terminal/core/user/user.sql";
import { orderTable } from "@terminal/core/order/order.sql";
import { eq } from "@terminal/core/drizzle/index";

export const handler = bus.subscriber(
[Order.Event.Created, User.Events.Updated],
Expand All @@ -11,6 +17,7 @@ export const handler = bus.subscriber(
switch (event.type) {
case "order.created": {
await Shippo.createShipment(event.properties.orderID);
await Template.sendOrderConfirmation(event.properties.orderID);
break;
}
case "user.updated": {
Expand Down

0 comments on commit 1b53851

Please sign in to comment.