feat: add resend emails

pull/715/head
Travis Fischer 2025-06-05 03:00:28 +07:00
rodzic 577bdca9c4
commit 21b54e1433
12 zmienionych plików z 1449 dodań i 31 usunięć

Wyświetl plik

@ -17,3 +17,5 @@ GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
AGENTIC_ADMIN_API_KEY=
RESEND_API_KEY=

Wyświetl plik

@ -29,6 +29,7 @@
"@agentic/openauth": "catalog:",
"@agentic/platform": "workspace:*",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-emails": "workspace:*",
"@agentic/platform-hono": "workspace:*",
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",

Wyświetl plik

@ -12,6 +12,8 @@ import { DrizzleAuthStorage } from '@/lib/drizzle-auth-storage'
import { env } from '@/lib/env'
import { getGitHubClient } from '@/lib/external/github'
import { resend } from './lib/external/resend'
// Initialize OpenAuth issuer which is a Hono app for all auth routes.
export const authRouter = issuer({
subjects,
@ -60,7 +62,9 @@ export const authRouter = issuer({
sendCode: async (email, code) => {
// TODO: Send email code to user
// eslint-disable-next-line no-console
console.log({ email, code })
console.log('sending verify code email', { email, code })
await resend.sendVerifyCodeEmail({ code, to: email })
},
validatePassword: (password) => {
if (password.length < 3) {

Wyświetl plik

@ -20,7 +20,9 @@ export const envSchema = baseEnvSchema
GITHUB_CLIENT_ID: z.string().nonempty(),
GITHUB_CLIENT_SECRET: z.string().nonempty(),
AGENTIC_ADMIN_API_KEY: z.string().nonempty()
AGENTIC_ADMIN_API_KEY: z.string().nonempty(),
RESEND_API_KEY: z.string().nonempty()
})
.strip()
export type Env = Simplify<ReturnType<typeof parseEnv>>

Wyświetl plik

@ -0,0 +1,7 @@
import { ResendEmailClient } from '@agentic/platform-emails'
import { env } from '@/lib/env'
export const resend = new ResendEmailClient({
apiKey: env.RESEND_API_KEY
})

Wyświetl plik

@ -0,0 +1,38 @@
{
"name": "@agentic/platform-emails",
"version": "0.1.0",
"description": "Email templates for the Agentic platform.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "UNLICENSED",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git",
"directory": "packages/emails"
},
"type": "module",
"source": "./src/index.ts",
"types": "./src/index.ts",
"sideEffects": false,
"exports": {
".": "./src/index.ts"
},
"scripts": {
"preview": "email dev --dir src/emails --port 3030",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:typecheck": "tsc --noEmit",
"test:unit": "vitest run"
},
"dependencies": {
"@agentic/platform-core": "workspace:*",
"@react-email/components": "^0.0.41",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"resend": "^4.5.1"
},
"devDependencies": {
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.6",
"react-email": "^4.0.16"
}
}

Wyświetl plik

@ -0,0 +1,97 @@
import {
Body,
Container,
Head,
Heading,
Hr,
Html,
Img,
Preview,
Section,
Tailwind,
Text
} from '@react-email/components'
import React from 'react'
export interface SendVerifyCodeEmailProps {
code: string
}
const logoUrl =
'https://mintlify.s3.us-west-1.amazonaws.com/agentic/media/agentic-logo-light.svg'
function SendVerifyCodeEmail({ code }: SendVerifyCodeEmailProps) {
const previewText = 'Verify your email address'
return (
<Html>
<Head />
<Tailwind>
<Body className='mx-auto my-auto bg-white px-2 font-sans'>
<Preview>{previewText}</Preview>
<Container className='mx-auto my-[40px] max-w-[465px] rounded border border-[#eaeaea] border-solid p-[20px]'>
<Section className='mt-[32px]'>
<Img
src={logoUrl}
width='203'
height='48'
alt='Agentic Logo'
className='mx-auto my-0'
/>
</Section>
<Heading className='mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black'>
Verify your email address
</Heading>
<Section style={verificationSection}>
<Text style={verifyText}>Verification code</Text>
<Text style={codeText}>{code}</Text>
</Section>
<Hr className='mx-0 my-[26px] w-full border border-[#eaeaea] border-solid' />
<Text className='text-[#666666] text-[12px] leading-[24px]'>
If you didnt sign up for Agentic, you can safely ignore this
email. Someone else might have typed your email address by
mistake.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
)
}
const text = {
color: '#333',
fontSize: '14px',
margin: '24px 0'
}
const verifyText = {
...text,
margin: 0,
textAlign: 'center' as const
}
const codeText = {
...text,
fontSize: '36px',
margin: '12px 0',
textAlign: 'center' as const
}
const verificationSection = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
SendVerifyCodeEmail.PreviewProps = {
code: '123456'
} as SendVerifyCodeEmailProps
export default SendVerifyCodeEmail

Wyświetl plik

@ -0,0 +1 @@
export * from './resend-email-client'

Wyświetl plik

@ -0,0 +1,45 @@
import { assert } from '@agentic/platform-core'
import React from 'react'
import { Resend as ResendClient } from 'resend'
import SendVerifyCodeEmail from './emails/send-verify-code-email'
export class ResendEmailClient {
protected readonly resend: ResendClient
protected readonly from: string
constructor({
apiKey,
from = 'Agentic <no-reply@notifications.agentic.so>'
}: {
apiKey: string
from?: string
}) {
assert(apiKey, 'ResendEmailClient missing required "apiKey"')
this.resend = new ResendClient(apiKey)
this.from = from
}
async sendVerifyCodeEmail({
code,
to
}: {
code: string
to: string
}): Promise<string> {
const result = await this.resend.emails.send({
from: this.from,
to,
subject: 'Verify your email address',
text: 'Verify your email address',
react: <SendVerifyCodeEmail code={code} />
})
if (result.error) {
throw new Error(result.error.message)
}
assert(result.data?.id, 'Failed to send verify code email')
return result.data.id
}
}

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src", "*.config.ts", "src/emails"],
"exclude": ["node_modules"]
}

Plik diff jest za duży Load Diff

Wyświetl plik

@ -8,32 +8,16 @@
## TODO
- **webapp**
- stripe
- stripe checkout
- stripe billing portal
- end-to-end working examples
- openapi
- mcp
- raw
- stripe
- re-add coupons
- declarative json-based pricing
- like https://github.com/tierrun/tier and Saasify
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
- https://blog.tier.run/tier-hello-world-demo
- stripe connect
- transactional emails
- (openauth password emails and `sendCode`)
- stripe-related billing emails
- auth
- custom auth pages for `openauth`
- re-add support for teams / organizations
- consider switching to [consola](https://github.com/unjs/consola) for logging?
- consider switching to `bun` (for `--hot` reloading!!)
- consider `projectName` and `projectSlug` or `projectIdentifier`?
- for clients and internal packages, importing some types from platform-types and some types from platform-api-client is confusing
- this actually causes problems because some types from the openapi version aren't compatible with the schema types like `PricingPlan`
- solved for now; revisit this to clean up in the future
- validate stability of pricing plan slugs across deployments
- same for pricing plan line-items
- replace `ms` package
- add username / team name blacklist
- admin, internal, mcp, sse, etc
- **API gateway**
@ -46,10 +30,8 @@
- => OpenAPI: `GET/POST/ETC originUrl/toolName` operation with transformed tool params
- RAW: `METHOD gateway.agentic.so/deploymentIdentifier/<pathname>`
- => Raw HTTP: `METHOD originUrl/<pathname>` simple HTTP proxy request
- add support for caching
- add support for custom headers on responses
- how to handle binary bodies and responses?
- signed requests
- public identifiers and validators
- revisit deployment identifiers so possibly be URL-friendly?
- move validators package into platform-types?
@ -57,11 +39,32 @@
- will remove ambiguity from `username/`
- make namespace optional? and require `@` prefix if so? like npm packages
- separate `parseToolIdentifier` from `parseDeploymentIdentifier` and `parseProjectIdentifier`?
- **KISS**
## TODO Post-MVP
- stripe
- re-add coupons
- declarative json-based pricing
- like https://github.com/tierrun/tier and Saasify
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
- https://blog.tier.run/tier-hello-world-demo
- stripe connect
- stripe-related billing emails
- re-add support for teams / organizations
- consider switching to [consola](https://github.com/unjs/consola) for logging?
- consider switching to `bun` (for `--hot` reloading!!)
- validate stability of pricing plan slugs across deployments
- same for pricing plan line-items
- replace `ms` package
- API gateway
- signed requests
- `@agentic/platform-hono`
- fix sentry middleware
- https://github.com/honojs/middleware/blob/main/packages/sentry/src/index.ts
- https://github.com/honojs/middleware/issues/943
- https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare
- additional transactional emails
## License