kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add resend emails
rodzic
577bdca9c4
commit
21b54e1433
|
@ -17,3 +17,5 @@ GITHUB_CLIENT_ID=
|
|||
GITHUB_CLIENT_SECRET=
|
||||
|
||||
AGENTIC_ADMIN_API_KEY=
|
||||
|
||||
RESEND_API_KEY=
|
||||
|
|
|
@ -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:*",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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
|
||||
})
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 didn’t 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
|
|
@ -0,0 +1 @@
|
|||
export * from './resend-email-client'
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src", "*.config.ts", "src/emails"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
1229
pnpm-lock.yaml
1229
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
45
readme.md
45
readme.md
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue