diff --git a/apps/api/.env.example b/apps/api/.env.example index cb2c83c1..635f0926 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -17,3 +17,8 @@ DIRECT_URL="postgresql://postgres:password@host:port/mydb" # Node Environment NODE_ENV="development" + +# Mail configuration +RESEND_API_KEY=your_api_key +MAIL_FROM=noreply@example.com +MAIL_FROM_NAME=Tracker Task diff --git a/apps/api/package.json b/apps/api/package.json index b707493e..91825e15 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -31,6 +31,8 @@ "@nestjs/swagger": "^11.2.6", "@prisma/adapter-pg": "^7.4.0", "@prisma/client": "^7.4.0", + "@react-email/components": "^1.0.10", + "@react-email/render": "^2.0.4", "@repo/types": "workspace:*", "argon2": "^0.44.0", "cookie-parser": "^1.4.7", @@ -41,7 +43,10 @@ "passport": "^0.7.0", "passport-jwt": "^4.0.1", "pg": "^8.18.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", "reflect-metadata": "^0.2.2", + "resend": "^6.9.4", "rxjs": "^7.8.2", "zod": "^4.3.6" }, @@ -58,6 +63,7 @@ "@types/passport": "^1.0.17", "@types/passport-jwt": "^4.0.1", "@types/pg": "^8.16.0", + "@types/react": "19.2.14", "@types/supertest": "^6.0.3", "@vitest/coverage-v8": "^4.0.18", "@vitest/ui": "^4.0.18", diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index 09e3d9e1..a0ea99c0 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -6,10 +6,12 @@ import { ConfigModule, ConfigService } from '@nestjs/config' import { getJwtConfig } from './config/jwt.config' import { JwtStrategy } from 'src/strategies/jwt.strategy' import { PassportModule } from '@nestjs/passport' +import { MailModule } from 'src/mail/mail.module' @Module({ imports: [ PassportModule, + MailModule, JwtModule.registerAsync({ imports: [ConfigModule], useFactory: getJwtConfig, diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 3eab476e..81808237 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config' import { ConflictException, Injectable, + Logger, NotFoundException, UnauthorizedException, } from '@nestjs/common' @@ -16,6 +17,7 @@ import { LoginRequestDto } from './dto/login.dto' import { isDev } from 'src/utils/is-dev.util' import { parseTTLToMs } from 'src/utils/ms.util' import { RedisService } from 'src/common/redis/redis.service' +import { MailService } from 'src/mail/mail.service' @Injectable() export class AuthService { @@ -23,12 +25,14 @@ export class AuthService { private readonly JWT_REFRESH_TOKEN_TTL private readonly COOKIE_DOMAIN: string private readonly COOKIE_TTL: string + private readonly logger = new Logger(AuthService.name) constructor( private readonly prismaService: PrismaService, private readonly configService: ConfigService, private readonly jwtService: JwtService, private readonly redisService: RedisService, + private readonly mailService: MailService, ) { this.JWT_ACCESS_TOKEN_TTL = this.configService.getOrThrow('JWT_ACCESS_TOKEN_TTL') @@ -60,6 +64,12 @@ export class AuthService { }, }) + try { + await this.mailService.sendWelcomeEmail(user.email, user.name) + } catch (error) { + this.logger.error('Не удалось отправить письмо приветствия', error) + } + return await this.auth(res, user.id) } diff --git a/apps/api/src/mail/config/mail.config.ts b/apps/api/src/mail/config/mail.config.ts new file mode 100644 index 00000000..9cc8a6b3 --- /dev/null +++ b/apps/api/src/mail/config/mail.config.ts @@ -0,0 +1,25 @@ +import { ConfigService } from '@nestjs/config' + +export function getMailConfig(configService: ConfigService) { + const host = configService.getOrThrow('MAIL_HOST') + const port = Number(configService.getOrThrow('MAIL_PORT')) + const secure = configService.getOrThrow('MAIL_SECURE') === 'true' + const user = configService.getOrThrow('MAIL_USER') + const pass = configService.getOrThrow('MAIL_PASSWORD') + const fromAddress = configService.getOrThrow('MAIL_FROM') + const fromName = configService.getOrThrow('MAIL_FROM_NAME') + + return { + host, + port, + secure, + auth: { + user, + pass, + }, + from: { + name: fromName, + address: fromAddress, + }, + } +} diff --git a/apps/api/src/mail/mail.constants.ts b/apps/api/src/mail/mail.constants.ts new file mode 100644 index 00000000..a84f88ca --- /dev/null +++ b/apps/api/src/mail/mail.constants.ts @@ -0,0 +1,2 @@ +export const MAIL_PROVIDER = 'MAIL_PROVIDER' +export const RESEND_CLIENT = 'RESEND_CLIENT' diff --git a/apps/api/src/mail/mail.module.ts b/apps/api/src/mail/mail.module.ts new file mode 100644 index 00000000..c33fb76f --- /dev/null +++ b/apps/api/src/mail/mail.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common' +import { MailService } from './mail.service' +import { MAIL_PROVIDER, RESEND_CLIENT } from './mail.constants' +import { ConfigService } from '@nestjs/config' +import { ResendMailProvider } from './providers/resend-mail.provider' +import { Resend } from 'resend' + +@Module({ + providers: [ + MailService, + { + provide: MAIL_PROVIDER, + useClass: ResendMailProvider, + }, + { + provide: RESEND_CLIENT, + useFactory: (configService: ConfigService) => + new Resend(configService.getOrThrow('RESEND_API_KEY')), + inject: [ConfigService], + }, + ], + exports: [MailService], +}) +export class MailModule {} diff --git a/apps/api/src/mail/mail.provider.ts b/apps/api/src/mail/mail.provider.ts new file mode 100644 index 00000000..c708bc4b --- /dev/null +++ b/apps/api/src/mail/mail.provider.ts @@ -0,0 +1,5 @@ +import type { MailPayload } from './mail.types' + +export interface MailProvider { + send(payload: MailPayload): Promise +} diff --git a/apps/api/src/mail/mail.service.ts b/apps/api/src/mail/mail.service.ts new file mode 100644 index 00000000..7825fd15 --- /dev/null +++ b/apps/api/src/mail/mail.service.ts @@ -0,0 +1,29 @@ +import { Inject, Injectable } from '@nestjs/common' +import type { MailProvider } from './mail.provider' +import { MAIL_PROVIDER } from './mail.constants' +import { ConfigService } from '@nestjs/config' +import { render } from '@react-email/render' +import { WELCOME_EMAIL_SUBJECT, WelcomeEmail } from './templates/welcome.email' + +@Injectable() +export class MailService { + constructor( + @Inject(MAIL_PROVIDER) + private readonly mailProvider: MailProvider, + private readonly configService: ConfigService, + ) {} + + async sendWelcomeEmail(email: string, name: string) { + const mailName = this.configService.getOrThrow('MAIL_FROM_NAME') + const mailAddress = this.configService.getOrThrow('MAIL_FROM') + + const html = await render(WelcomeEmail({ name })) + + await this.mailProvider.send({ + from: `${mailName} <${mailAddress}>`, + to: email, + subject: WELCOME_EMAIL_SUBJECT, + html, + }) + } +} diff --git a/apps/api/src/mail/mail.types.ts b/apps/api/src/mail/mail.types.ts new file mode 100644 index 00000000..672a7fba --- /dev/null +++ b/apps/api/src/mail/mail.types.ts @@ -0,0 +1,7 @@ +export type MailPayload = { + from: string + to: string | string[] + subject: string + text?: string + html?: string +} diff --git a/apps/api/src/mail/providers/resend-mail.provider.ts b/apps/api/src/mail/providers/resend-mail.provider.ts new file mode 100644 index 00000000..3f7f181b --- /dev/null +++ b/apps/api/src/mail/providers/resend-mail.provider.ts @@ -0,0 +1,23 @@ +import { Inject, Injectable } from '@nestjs/common' +import { Resend } from 'resend' +import type { MailProvider } from '../mail.provider' +import type { MailPayload } from '../mail.types' +import { RESEND_CLIENT } from '../mail.constants' + +@Injectable() +export class ResendMailProvider implements MailProvider { + constructor(@Inject(RESEND_CLIENT) private readonly resend: Resend) {} + + async send(payload: MailPayload): Promise { + const { error } = await this.resend.emails.send({ + from: payload.from, + to: payload.to, + subject: payload.subject, + html: payload.html, + }) + + if (error) { + throw error + } + } +} diff --git a/apps/api/src/mail/templates/welcome.email.tsx b/apps/api/src/mail/templates/welcome.email.tsx new file mode 100644 index 00000000..6fa42d80 --- /dev/null +++ b/apps/api/src/mail/templates/welcome.email.tsx @@ -0,0 +1,126 @@ +import { + Body, + Button, + Container, + Head, + Heading, + Hr, + Html, + Preview, + Section, + Text, +} from '@react-email/components' + +interface Props { + name: string +} + +export const WELCOME_EMAIL_SUBJECT = 'Добро пожаловать в Tracker Task' + +export const WelcomeEmail = ({ name }: Props) => ( + + + Рады видеть тебя в Tracker Task, {name}! + + +
+ Tracker Task +
+ +
+ Привет, {name}! 👋 + + + Твой аккаунт успешно создан. Теперь ты можешь управлять задачами, создавать + проекты и работать в команде. + + + Начни прямо сейчас: + +
+ {/* Заменить href позже на реальный url */} + +
+ +
+ + + Если ты не регистрировался — просто проигнорируй это письмо. + +
+
+ + +) + +const body = { + backgroundColor: '#f6f9fc', + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', +} + +const container = { + margin: '40px auto', + maxWidth: '560px', +} + +const logoSection = { + padding: '32px 40px 0', +} + +const logo = { + fontSize: '20px', + fontWeight: '700', + color: '#1a1a1a', + margin: '0', +} + +const content = { + backgroundColor: '#ffffff', + borderRadius: '12px', + padding: '40px', + marginTop: '16px', + boxShadow: '0 1px 3px rgba(0,0,0,0.08)', +} + +const heading = { + fontSize: '24px', + fontWeight: '700', + color: '#1a1a1a', + margin: '0 0 20px', +} + +const paragraph = { + fontSize: '16px', + lineHeight: '24px', + color: '#4a5568', + margin: '0 0 16px', +} + +const buttonSection = { + margin: '28px 0', +} + +const button = { + backgroundColor: '#1a1a1a', + borderRadius: '8px', + color: '#ffffff', + fontSize: '15px', + fontWeight: '600', + padding: '14px 28px', + textDecoration: 'none', + display: 'inline-block', +} + +const hr = { + border: 'none', + borderTop: '1px solid #e2e8f0', + margin: '28px 0 20px', +} + +const footer = { + fontSize: '13px', + color: '#a0aec0', + margin: '0', +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index a471e648..9b342111 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,7 +1,8 @@ { - "extends": "@repo/typescript-config/nestjs.json", - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist" - } + "extends": "@repo/typescript-config/nestjs.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "jsx": "react-jsx" + } } diff --git a/docs/backend/mail.md b/docs/backend/mail.md new file mode 100644 index 00000000..bd35c690 --- /dev/null +++ b/docs/backend/mail.md @@ -0,0 +1,130 @@ +# MailModule + +Инфраструктурный модуль для отправки email. Используется бизнес-модулями (например, `AuthModule`) для отправки писем пользователям. + +## Архитектура + +``` +AuthService + └── MailService # бизнес-методы отправки (sendWelcomeEmail и т.п.) + └── MailProvider # абстракция провайдера (интерфейс) + └── ResendMailProvider # конкретная реализация через Resend API +``` + +Такая структура позволяет заменить провайдера отправки (например, с Resend на SendGrid) без изменений в `MailService` и бизнес-модулях. + +### Файлы модуля + +``` +src/mail/ + mail.module.ts # сборка DI: регистрация провайдеров и Resend-клиента + mail.service.ts # бизнес-методы отправки писем + mail.provider.ts # интерфейс MailProvider + mail.types.ts # тип MailPayload + mail.constants.ts # DI-токены: MAIL_PROVIDER, RESEND_CLIENT + config/ + mail.config.ts # чтение переменных окружения + providers/ + resend-mail.provider.ts # реализация через Resend HTTP API + templates/ + welcome.email.tsx # React Email шаблон приветственного письма +``` + +## Переменные окружения + +| Переменная | Описание | Пример | +| ---------------- | ----------------------- | --------------------- | +| `RESEND_API_KEY` | API-ключ сервиса Resend | `re_xxxxxxxxxxxxxxxx` | +| `MAIL_FROM` | Email адрес отправителя | `noreply@example.com` | +| `MAIL_FROM_NAME` | Имя отправителя | `Tracker Task` | + +## Как добавить новое письмо + +**1. Создать шаблон** в `templates/`: + +```tsx +// templates/invite.email.tsx +export const INVITE_EMAIL_SUBJECT = 'Вас пригласили в команду' + +export const InviteEmail = ({ name, teamName }: Props) => ... +``` + +**2. Добавить метод в `MailService`:** + +```typescript +async sendInviteEmail(email: string, name: string, teamName: string) { + const html = await render(InviteEmail({ name, teamName })) + + await this.mailProvider.send({ + from: `${this.mailName} <${this.mailAddress}>`, + to: email, + subject: INVITE_EMAIL_SUBJECT, + html, + }) +} +``` + +**3. Вызвать из нужного бизнес-сервиса:** + +```typescript +await this.mailService.sendInviteEmail(user.email, user.name, team.name) +``` + +## Шаблоны писем + +Письма верстаются на **React Email** (`@react-email/components`). + +Это React-компоненты, которые рендерятся в HTML-строку на сервере через `render()` из `@react-email/render`. Никакого браузера, никакого DOM — просто шаблонизатор. + +Плюсы подхода: + +- компоненты и переиспользование +- TypeScript нативно +- превью письма в браузере через `react-email` dev-сервер +- email-совместимый HTML из коробки + +## Почему Resend API, а не SMTP + +Есть два способа отправки email: + +**SMTP** — прямое TCP-соединение с почтовым сервером на портах 465/587. Исторический стандарт. Проблемы: + +- соединение может блокироваться файрволами или ISP +- сложнее отлаживать +- нет встроенной аналитики + +**HTTP API (Resend)** — обычный HTTPS-запрос на порт 443. Resend принимает письмо и доставляет его через свою SMTP-инфраструктуру. + +Почему выбрали API: + +- порт 443 открыт везде, проблем с сетью нет +- Resend возвращает явный `{ data, error }` — легче обрабатывать ошибки +- есть дашборд с историей отправок +- официальный Node.js SDK + +## Обработка ошибок + +Resend SDK не бросает исключения — возвращает `{ data, error }`. `ResendMailProvider` явно проверяет `error` и бросает исключение: + +```typescript +const { data, error } = await this.resend.emails.send({...}) +if (error) throw error +``` + +В `AuthService` отправка письма обёрнута в `try/catch` — ошибка логируется, но не ломает регистрацию: + +```typescript +try { + await this.mailService.sendWelcomeEmail(user.email, user.name) +} catch (error) { + this.logger.error('Не удалось отправить письмо приветствия', error) +} +``` + +## Настройка Resend + +1. Зарегистрироваться на [resend.com](https://resend.com) +2. Добавить и верифицировать домен (DNS-записи) +3. Создать API-ключ в дашборде +4. Прописать ключ в `RESEND_API_KEY` +5. Указать в `MAIL_FROM` адрес на верифицированном домене diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c90a33e5..b8210ea9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,12 @@ importers: '@prisma/client': specifier: ^7.4.0 version: 7.4.0(prisma@7.4.0(@types/react@19.2.14)(magicast@0.3.5)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.5.4))(typescript@5.5.4) + '@react-email/components': + specifier: ^1.0.10 + version: 1.0.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/render': + specifier: ^2.0.4 + version: 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@repo/types': specifier: workspace:* version: link:../../packages/types @@ -110,9 +116,18 @@ importers: pg: specifier: ^8.18.0 version: 8.18.0 + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + resend: + specifier: ^6.9.4 + version: 6.9.4(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) rxjs: specifier: ^7.8.2 version: 7.8.2 @@ -156,6 +171,9 @@ importers: '@types/pg': specifier: ^8.16.0 version: 8.16.0 + '@types/react': + specifier: 19.2.14 + version: 19.2.14 '@types/supertest': specifier: ^6.0.3 version: 6.0.3 @@ -2439,6 +2457,165 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-email/body@0.3.0': + resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/button@0.2.1': + resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/code-block@0.2.1': + resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/code-inline@0.0.6': + resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/column@0.0.14': + resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/components@1.0.10': + resolution: {integrity: sha512-r/BnqfAjr3apcvn/NDx2DqNRD5BP5wZLRdjn2IVHXjt4KmQ5RHWSCAvFiXAzRHys1BWQ2zgIc7cpWePUcAl+nw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/container@0.0.16': + resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/font@0.0.10': + resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/head@0.0.13': + resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/heading@0.0.16': + resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/hr@0.0.12': + resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/html@0.0.12': + resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/img@0.0.12': + resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/link@0.0.13': + resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/markdown@0.0.18': + resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/preview@0.0.14': + resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/render@2.0.4': + resolution: {integrity: sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/row@0.0.13': + resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/section@0.0.17': + resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/tailwind@2.0.6': + resolution: {integrity: sha512-3PgL/GYWmgS+puLPQ2aLlsplHSOFztRl70fowBkbLIb8ZUIgvx5YId6zYCCHeM2+DQ/EG3iXXqLNTahVztuMqQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@react-email/body': '>=0' + '@react-email/button': '>=0' + '@react-email/code-block': '>=0' + '@react-email/code-inline': '>=0' + '@react-email/container': '>=0' + '@react-email/heading': '>=0' + '@react-email/hr': '>=0' + '@react-email/img': '>=0' + '@react-email/link': '>=0' + '@react-email/preview': '>=0' + '@react-email/text': '>=0' + react: ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@react-email/body': + optional: true + '@react-email/button': + optional: true + '@react-email/code-block': + optional: true + '@react-email/code-inline': + optional: true + '@react-email/container': + optional: true + '@react-email/heading': + optional: true + '@react-email/hr': + optional: true + '@react-email/img': + optional: true + '@react-email/link': + optional: true + '@react-email/preview': + optional: true + + '@react-email/text@0.1.6': + resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2595,10 +2772,16 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -3979,6 +4162,19 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -4051,6 +4247,10 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4283,6 +4483,9 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -4581,6 +4784,13 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -4968,6 +5178,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -5196,6 +5409,11 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5555,6 +5773,9 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -5620,6 +5841,9 @@ packages: pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -5710,6 +5934,9 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postal-mime@2.7.3: + resolution: {integrity: sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==} + postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -5780,6 +6007,10 @@ packages: typescript: optional: true + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -5951,6 +6182,15 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resend@6.9.4: + resolution: {integrity: sha512-/M3dsJzu5OgozqVsA4Psd/1L7EdePgOIIxClas453GOQYFG3VHc2ZyCHZFlvqsc9aZCCd2BJRRqZgWC8D9c7/g==} + engines: {node: '>=20'} + peerDependencies: + '@react-email/render': '*' + peerDependenciesMeta: + '@react-email/render': + optional: true + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -6041,6 +6281,9 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -6171,6 +6414,9 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -6320,6 +6566,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svix@1.86.0: + resolution: {integrity: sha512-/HTvXwjLJe1l/MsLXAO1ddCYxElJk4eNR4DzOjDOEmGrPN/3BtBE8perGwMAaJ2sT5T172VkBYzmHcjUfM1JRQ==} + swagger-ui-dist@5.31.0: resolution: {integrity: sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==} @@ -6334,6 +6583,9 @@ packages: tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tailwindcss@4.2.0: resolution: {integrity: sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==} @@ -6658,6 +6910,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -9020,6 +9276,130 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-email/body@0.3.0(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/button@0.2.1(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/code-block@0.2.1(react@19.2.4)': + dependencies: + prismjs: 1.30.0 + react: 19.2.4 + + '@react-email/code-inline@0.0.6(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/column@0.0.14(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/components@1.0.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-email/body': 0.3.0(react@19.2.4) + '@react-email/button': 0.2.1(react@19.2.4) + '@react-email/code-block': 0.2.1(react@19.2.4) + '@react-email/code-inline': 0.0.6(react@19.2.4) + '@react-email/column': 0.0.14(react@19.2.4) + '@react-email/container': 0.0.16(react@19.2.4) + '@react-email/font': 0.0.10(react@19.2.4) + '@react-email/head': 0.0.13(react@19.2.4) + '@react-email/heading': 0.0.16(react@19.2.4) + '@react-email/hr': 0.0.12(react@19.2.4) + '@react-email/html': 0.0.12(react@19.2.4) + '@react-email/img': 0.0.12(react@19.2.4) + '@react-email/link': 0.0.13(react@19.2.4) + '@react-email/markdown': 0.0.18(react@19.2.4) + '@react-email/preview': 0.0.14(react@19.2.4) + '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/row': 0.0.13(react@19.2.4) + '@react-email/section': 0.0.17(react@19.2.4) + '@react-email/tailwind': 2.0.6(@react-email/body@0.3.0(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4) + '@react-email/text': 0.1.6(react@19.2.4) + react: 19.2.4 + transitivePeerDependencies: + - react-dom + + '@react-email/container@0.0.16(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/font@0.0.10(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/head@0.0.13(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/heading@0.0.16(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/hr@0.0.12(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/html@0.0.12(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/img@0.0.12(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/link@0.0.13(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/markdown@0.0.18(react@19.2.4)': + dependencies: + marked: 15.0.12 + react: 19.2.4 + + '@react-email/preview@0.0.14(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + html-to-text: 9.0.5 + prettier: 3.8.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@react-email/row@0.0.13(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/section@0.0.17(react@19.2.4)': + dependencies: + react: 19.2.4 + + '@react-email/tailwind@2.0.6(@react-email/body@0.3.0(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)': + dependencies: + '@react-email/text': 0.1.6(react@19.2.4) + react: 19.2.4 + tailwindcss: 4.1.18 + optionalDependencies: + '@react-email/body': 0.3.0(react@19.2.4) + '@react-email/button': 0.2.1(react@19.2.4) + '@react-email/code-block': 0.2.1(react@19.2.4) + '@react-email/code-inline': 0.0.6(react@19.2.4) + '@react-email/container': 0.0.16(react@19.2.4) + '@react-email/heading': 0.0.16(react@19.2.4) + '@react-email/hr': 0.0.12(react@19.2.4) + '@react-email/img': 0.0.12(react@19.2.4) + '@react-email/link': 0.0.13(react@19.2.4) + '@react-email/preview': 0.0.14(react@19.2.4) + + '@react-email/text@0.1.6(react@19.2.4)': + dependencies: + react: 19.2.4 + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/pluginutils@5.3.0(rollup@4.57.1)': @@ -9109,8 +9489,15 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@sindresorhus/merge-streams@4.0.0': {} + '@stablelib/base64@1.0.1': {} + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -10769,6 +11156,24 @@ snapshots: dom-accessibility-api@0.6.3: {} + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -10835,6 +11240,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@4.5.0: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -11224,6 +11631,8 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-sha256@1.3.0: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -11524,6 +11933,21 @@ snapshots: html-escaper@2.0.2: {} + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -11885,6 +12309,8 @@ snapshots: kleur@4.1.5: {} + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -12076,6 +12502,8 @@ snapshots: make-error@1.3.6: {} + marked@15.0.12: {} + math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -12502,6 +12930,11 @@ snapshots: parse-ms@4.0.0: {} + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} passport-jwt@4.0.1: @@ -12530,7 +12963,7 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 path-scurry@2.0.1: dependencies: @@ -12554,6 +12987,8 @@ snapshots: pause@0.0.1: {} + peberminta@0.9.0: {} + perfect-debounce@1.0.0: {} pg-cloudflare@1.3.0: @@ -12631,6 +13066,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + postal-mime@2.7.3: {} + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -12694,6 +13131,8 @@ snapshots: - react - react-dom + prismjs@1.30.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -12929,6 +13368,13 @@ snapshots: require-from-string@2.0.2: {} + resend@6.9.4(@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + dependencies: + postal-mime: 2.7.3 + svix: 1.86.0 + optionalDependencies: + '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -13056,6 +13502,10 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@6.3.1: {} semver@7.7.4: {} @@ -13270,6 +13720,11 @@ snapshots: standard-as-callback@2.1.0: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + statuses@2.0.2: {} std-env@3.10.0: {} @@ -13456,6 +13911,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svix@1.86.0: + dependencies: + standardwebhooks: 1.0.0 + uuid: 10.0.0 + swagger-ui-dist@5.31.0: dependencies: '@scarf/scarf': 1.4.0 @@ -13466,6 +13926,8 @@ snapshots: tailwind-merge@3.5.0: {} + tailwindcss@4.1.18: {} + tailwindcss@4.2.0: {} tapable@2.3.0: {} @@ -13820,6 +14282,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + v8-compile-cache-lib@3.0.1: {} valibot@1.2.0(typescript@5.5.4):