diff --git a/apps/app/app/actions/__tests__/booking.test.ts b/apps/app/app/actions/__tests__/booking.test.ts index aca2cac..21f356a 100644 --- a/apps/app/app/actions/__tests__/booking.test.ts +++ b/apps/app/app/actions/__tests__/booking.test.ts @@ -3,6 +3,16 @@ import { createBooking } from "../booking"; import { db } from "@repo/db"; import type { Booking, EventType } from "@prisma/client"; +const { mockEmailSend } = vi.hoisted(() => ({ + mockEmailSend: vi.fn(), +})); + +vi.mock("resend", () => ({ + Resend: class MockResend { + emails = { send: mockEmailSend }; + }, +})); + vi.mock("@repo/db", () => ({ db: { eventType: { @@ -12,6 +22,9 @@ vi.mock("@repo/db", () => ({ findFirst: vi.fn(), create: vi.fn(), }, + user: { + findUnique: vi.fn(), + }, }, })); @@ -22,6 +35,17 @@ vi.mock("next/cache", () => ({ describe("createBooking Server Action", () => { beforeEach(() => { vi.clearAllMocks(); + process.env.RESEND_API_KEY = "re_test_key"; + vi.mocked(db.user.findUnique).mockResolvedValue({ + id: "usr_host", + name: "Test Host", + username: "test-host", + email: "host@example.com", + } as never); + mockEmailSend.mockResolvedValue({ + data: { id: "email_123" }, + error: null, + }); }); const validPayload = { @@ -62,6 +86,7 @@ describe("createBooking Server Action", () => { duration: 30, userId: "usr_host", isArchived: false, + title: "Discovery Call", } as unknown as EventType); vi.mocked(db.booking.findFirst).mockResolvedValue({ @@ -81,6 +106,7 @@ describe("createBooking Server Action", () => { duration: 30, userId: "usr_host", isArchived: false, + title: "Discovery Call", } as unknown as EventType); vi.mocked(db.booking.findFirst).mockResolvedValue(null); @@ -92,6 +118,10 @@ describe("createBooking Server Action", () => { expect(response.success).toBe(true); if (response.success) { expect(response.bookingId).toBe("bk_new_789"); + expect(response.emailDelivery).toEqual({ + status: "SENT", + recipient: "alice@example.com", + }); } }); @@ -102,6 +132,7 @@ describe("createBooking Server Action", () => { duration: 30, userId: "usr_host", isArchived: false, + title: "Discovery Call", } as unknown as EventType); // 2. Mock that the slot is completely free @@ -135,6 +166,7 @@ describe("createBooking Server Action", () => { duration: 30, userId: "usr_host", isArchived: false, + title: "Discovery Call", } as unknown as EventType); vi.mocked(db.booking.findFirst).mockResolvedValue(null); vi.mocked(db.booking.create).mockResolvedValue({ @@ -159,4 +191,32 @@ describe("createBooking Server Action", () => { }), ); }); + + it("keeps the booking successful when confirmation email delivery fails", async () => { + vi.mocked(db.eventType.findUnique).mockResolvedValue({ + id: "evt_123", + duration: 30, + userId: "usr_host", + isArchived: false, + title: "Discovery Call", + } as unknown as EventType); + vi.mocked(db.booking.findFirst).mockResolvedValue(null); + vi.mocked(db.booking.create).mockResolvedValue({ + id: "bk_email_failed", + } as unknown as Booking); + mockEmailSend.mockResolvedValue({ + data: null, + error: { message: "Provider unavailable" }, + }); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + const response = await createBooking(validPayload); + + expect(response.success).toBe(true); + if (response.success) { + expect(response.bookingId).toBe("bk_email_failed"); + expect(response.emailDelivery.status).toBe("FAILED"); + } + consoleSpy.mockRestore(); + }); }); diff --git a/apps/app/app/actions/booking.ts b/apps/app/app/actions/booking.ts index ec91ff5..98caeab 100644 --- a/apps/app/app/actions/booking.ts +++ b/apps/app/app/actions/booking.ts @@ -3,9 +3,25 @@ import { db } from "@repo/db"; import { createBookingSchema, type CreateBookingInput } from "@/lib/schemas"; import { revalidatePath } from "next/cache"; +import { Resend } from "resend"; +import { createEvent, type EventAttributes } from "ics"; +import { BookingConfirmationEmail } from "@/components/emails/booking-confirmation"; +import { render } from "@react-email/components"; +import { formatInTimeZone } from "date-fns-tz"; + +export type EmailDeliveryResult = { + status: "SENT" | "FAILED"; + recipient: string; +}; + +export type CreateBookingSuccess = { + success: true; + bookingId: string; + emailDelivery: EmailDeliveryResult; +}; export type CreateBookingResult = - | { success: true; bookingId: string } + | CreateBookingSuccess | { success: false; error: string; fieldErrors?: Record }; export async function createBooking( @@ -32,16 +48,26 @@ export async function createBooking( } = result.data; try { - // 1. Fetch EventType details to determine duration & active status + // 1. Fetch EventType AND Host details const eventType = await db.eventType.findUnique({ where: { id: eventTypeId }, - select: { duration: true, userId: true, isArchived: true }, + select: { duration: true, userId: true, isArchived: true, title: true }, + }); + + const host = await db.user.findUnique({ + where: { id: hostId }, + select: { name: true, username: true, email: true }, }); - if (!eventType || eventType.isArchived || eventType.userId !== hostId) { + if ( + !eventType || + eventType.isArchived || + eventType.userId !== hostId || + !host + ) { return { success: false, - error: "This event type is no longer active or available.", + error: "This event type or host is no longer active or available.", }; } @@ -50,7 +76,7 @@ export async function createBooking( startDateTime.getTime() + eventType.duration * 60 * 1000, ); - // 2. Prevent Double Booking / Race Condition (Check overlapping bookings) + // 2. Prevent Double Booking const conflictingBooking = await db.booking.findFirst({ where: { hostId, @@ -70,7 +96,10 @@ export async function createBooking( }; } - // 3. Create Booking Record in Database + // 3. Generate a Unique ID for Calendar Sync (Idempotency) + const icsUid = crypto.randomUUID() + "@slotsyncro.com"; + + // 4. Create Booking Record in Database const newBooking = await db.booking.create({ data: { eventTypeId, @@ -82,14 +111,111 @@ export async function createBooking( startTime: startDateTime, endTime: endDateTime, status: "ACCEPTED", + icsUid, // ✨ Save the unique calendar ID }, }); + // 5. Generate the .ics Calendar File + const icsEvent: EventAttributes = { + start: [ + startDateTime.getUTCFullYear(), + startDateTime.getUTCMonth() + 1, // ics months are 1-indexed + startDateTime.getUTCDate(), + startDateTime.getUTCHours(), + startDateTime.getUTCMinutes(), + ], + end: [ + endDateTime.getUTCFullYear(), + endDateTime.getUTCMonth() + 1, + endDateTime.getUTCDate(), + endDateTime.getUTCHours(), + endDateTime.getUTCMinutes(), + ], + startInputType: "utc", + startOutputType: "utc", + title: `${eventType.title} with ${host.name || host.username}`, + description: guestNotes || "Scheduled via SlotSyncro", + attendees: [{ name: guestName, email: guestEmail }], + uid: icsUid, + ...(host.email + ? { + organizer: { + name: host.name || host.username || "Host", + email: host.email, + }, + } + : {}), + }; + + const { value: icsContent, error: icsError } = createEvent(icsEvent); + if (icsError) console.error("ICS Generation Error:", icsError); + + // Email delivery is a secondary side effect. Once the booking exists, an + // email provider failure must not make the client retry the booking itself. + let emailDelivery: EmailDeliveryResult = { + status: "FAILED", + recipient: guestEmail, + }; + + if (icsContent) { + try { + const apiKey = process.env.RESEND_API_KEY; + if (!apiKey) { + throw new Error("RESEND_API_KEY is not configured"); + } + + const emailHtml = await render( + BookingConfirmationEmail({ + guestName, + hostName: host.name || host.username || "Host", + eventTitle: eventType.title, + date: formatInTimeZone( + startDateTime, + guestTimeZone, + "EEEE, MMMM d, yyyy", + ), + time: formatInTimeZone( + startDateTime, + guestTimeZone, + "h:mm a zzz", + ), + }), + ); + + const resend = new Resend(apiKey); + const { error: resendError } = await resend.emails.send({ + from: "SlotSyncro ", // Testing domain + to: guestEmail, + subject: `Confirmed: ${eventType.title} with ${host.name || host.username}`, + html: emailHtml, + attachments: [ + { + filename: "invite.ics", + content: Buffer.from(icsContent).toString("base64"), + }, + ], + }); + + if (resendError) { + throw new Error(resendError.message); + } + + emailDelivery = { status: "SENT", recipient: guestEmail }; + } catch (emailError) { + console.error( + `Booking ${newBooking.id} was created, but confirmation email delivery failed:`, + emailError, + ); + } + } + revalidatePath("/[username]/[slug]", "page"); + revalidatePath("/(dashboard)/bookings", "page"); return { success: true, bookingId: newBooking.id, + emailDelivery, }; } catch (error) { console.error("Failed to create booking:", error); diff --git a/apps/app/components/booking/booking-form.tsx b/apps/app/components/booking/booking-form.tsx index 8a8792f..c3318e7 100644 --- a/apps/app/components/booking/booking-form.tsx +++ b/apps/app/components/booking/booking-form.tsx @@ -4,7 +4,10 @@ import { useTransition } from "react"; import { useForm, type Resolver } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { createBookingSchema, type CreateBookingInput } from "@/lib/schemas"; -import { createBooking } from "@/app/actions/booking"; +import { + createBooking, + type CreateBookingSuccess, +} from "@/app/actions/booking"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -23,7 +26,7 @@ interface BookingFormProps { guestTimeZone: string; loggedInUser?: { id: string; name: string; email: string } | null; onBack: () => void; - onSuccess: (bookingId: string) => void; + onSuccess: (confirmation: CreateBookingSuccess) => void; } export function BookingForm({ @@ -66,7 +69,7 @@ export function BookingForm({ startTransition(async () => { const response = await createBooking(data); if (response.success) { - onSuccess(response.bookingId); + onSuccess(response); } else { if (response.fieldErrors) { Object.entries(response.fieldErrors).forEach(([field, messages]) => { @@ -180,7 +183,7 @@ export function BookingForm({ {isPending ? ( <> - Scheduling Meeting... + Confirming booking and sending invitation... ) : ( "Confirm & Schedule" diff --git a/apps/app/components/booking/booking-success.tsx b/apps/app/components/booking/booking-success.tsx index dc15c01..7db5853 100644 --- a/apps/app/components/booking/booking-success.tsx +++ b/apps/app/components/booking/booking-success.tsx @@ -1,7 +1,16 @@ // apps/app/components/booking/booking-success.tsx -import { CheckCircle2, Calendar, Clock, Globe } from "lucide-react"; +import { + CheckCircle2, + Calendar, + Clock, + Globe, + MailCheck, + MailWarning, + Paperclip, +} from "lucide-react"; import { format } from "date-fns"; import { Button } from "@/components/ui/button"; +import type { EmailDeliveryResult } from "@/app/actions/booking"; interface BookingSuccessProps { eventTitle: string; @@ -9,6 +18,7 @@ interface BookingSuccessProps { startTime: Date; duration: number; timeZone: string; + emailDelivery: EmailDeliveryResult; onBookAnother?: () => void; } @@ -18,6 +28,7 @@ export function BookingSuccess({ startTime, duration, timeZone, + emailDelivery, onBookAnother, }: BookingSuccessProps) { return ( @@ -31,11 +42,46 @@ export function BookingSuccess({ You're Scheduled!

- A calendar invitation and confirmation details have been sent to your - email. + Your meeting has been successfully booked.

+
+
+ {emailDelivery.status === "SENT" ? ( + + ) : ( + + )} +
+

+ {emailDelivery.status === "SENT" + ? "Confirmation email sent" + : "Confirmation email could not be sent"} +

+

+ {emailDelivery.status === "SENT" + ? `Sent to ${emailDelivery.recipient}` + : `Your booking is confirmed, but delivery to ${emailDelivery.recipient} failed.`} +

+ {emailDelivery.status === "SENT" && ( +

+ + Calendar invitation attached +

+ )} +
+
+
+
{eventTitle} with {hostName} diff --git a/apps/app/components/booking/booking-view.tsx b/apps/app/components/booking/booking-view.tsx index fc760f2..6090b85 100644 --- a/apps/app/components/booking/booking-view.tsx +++ b/apps/app/components/booking/booking-view.tsx @@ -20,6 +20,7 @@ import { } from "@/lib/availability/engine"; import { BookingForm } from "./booking-form"; import { BookingSuccess } from "./booking-success"; +import type { CreateBookingSuccess } from "@/app/actions/booking"; export type BookingStep = "SELECTING_SLOT" | "ENTERING_DETAILS" | "CONFIRMED"; @@ -74,6 +75,8 @@ export function BookingView({ new Date(), ); const [selectedSlot, setSelectedSlot] = useState(null); + const [confirmation, setConfirmation] = + useState(null); const guestTimeZone = useMemo(() => { return ( @@ -235,21 +238,26 @@ export function BookingView({ selectedSlot={selectedSlot} guestTimeZone={guestTimeZone} onBack={() => setStep("SELECTING_SLOT")} - onSuccess={() => setStep("CONFIRMED")} + onSuccess={(result) => { + setConfirmation(result); + setStep("CONFIRMED"); + }} loggedInUser={loggedInUser} /> )} {/* Step 3: Success Confirmation State */} - {step === "CONFIRMED" && selectedSlot && ( + {step === "CONFIRMED" && selectedSlot && confirmation && ( { setSelectedSlot(null); + setConfirmation(null); setStep("SELECTING_SLOT"); }} /> diff --git a/apps/app/components/emails/booking-confirmation.tsx b/apps/app/components/emails/booking-confirmation.tsx new file mode 100644 index 0000000..4eb6e37 --- /dev/null +++ b/apps/app/components/emails/booking-confirmation.tsx @@ -0,0 +1,78 @@ +import { + Body, + Container, + Head, + Heading, + Html, + Preview, + Section, + Text, + Tailwind, +} from "@react-email/components"; + +interface BookingConfirmationEmailProps { + guestName: string; + hostName: string; + eventTitle: string; + date: string; + time: string; + meetingUrl?: string; +} + +export const BookingConfirmationEmail = ({ + guestName = "Guest", + hostName = "Host", + eventTitle = "Meeting", + date = "January 1, 2026", + time = "10:00 AM", + meetingUrl, +}: BookingConfirmationEmailProps) => { + const previewText = `Your meeting with ${hostName} is confirmed!`; + + return ( + + + {previewText} + + + + + Meeting Confirmed + + + Hi {guestName}, + + + Your {eventTitle} with {hostName} has been + successfully scheduled. + + +
+ + 🗓 Date: {date} + + + ⏰ Time: {time} + + {meetingUrl && ( + + 🔗 Location:{" "} + + {meetingUrl} + + + )} +
+ + + A calendar invitation (.ics) has been attached to this email. + Please add it to your calendar. + +
+ +
+ + ); +}; + +export default BookingConfirmationEmail; diff --git a/apps/app/package.json b/apps/app/package.json index 6abe9cc..38e1832 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -16,11 +16,14 @@ "@hookform/resolvers": "^5.7.1", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-slot": "^1.3.3", + "@react-email/components": "^1.0.12", + "@react-email/render": "^2.1.0", "@repo/db": "workspace:*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.4.0", "date-fns-tz": "^3.2.0", + "ics": "^3.12.0", "lucide-react": "^1.28.0", "next": "16.2.12", "next-auth": "5.0.0-beta.32", @@ -30,6 +33,7 @@ "react-day-picker": "10.0.1", "react-dom": "19.2.4", "react-hook-form": "^7.84.0", + "resend": "^6.22.0", "shadcn": "^4.16.1", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 1be942f..e1b1af4 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -137,6 +137,16 @@ model UserAvailability { @@index([userId]) } +// Define an enum to track the type of meeting location +enum MeetingProvider { + GOOGLE_MEET + ZOOM + MICROSOFT_TEAMS + PHONE + IN_PERSON + OTHER +} + model Booking { id String @id @default(cuid()) @@ -154,8 +164,14 @@ model Booking { startTime DateTime endTime DateTime - status BookingStatus @default(ACCEPTED) - googleMeetLink String? + status BookingStatus @default(ACCEPTED) + + // ✨ REPLACED: Generic fields for any meeting platform + meetingUrl String? // e.g. "https://zoom.us/j/123456789" + meetingProvider MeetingProvider? // e.g. ZOOM + + // Unique identifier for iCalendar external sync + icsUid String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e6fddb..a0cf084 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,13 +22,19 @@ importers: version: 1.6.0(@date-fns/tz@1.5.0)(@types/react@19.2.17)(date-fns@4.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@hookform/resolvers': specifier: ^5.7.1 - version: 5.7.1(@standard-schema/spec@1.1.0)(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(effect@3.20.0)(react-hook-form@7.84.0(react@19.2.4))(valibot@1.4.2(typescript@5.9.3))(zod@4.4.3) + version: 5.7.1(@standard-schema/spec@1.1.0)(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(effect@3.20.0)(react-hook-form@7.84.0(react@19.2.4))(valibot@1.4.2(typescript@5.9.3))(yup@1.7.1)(zod@4.4.3) '@radix-ui/react-label': specifier: ^2.1.15 version: 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-slot': specifier: ^1.3.3 version: 1.3.3(@types/react@19.2.17)(react@19.2.4) + '@react-email/components': + specifier: ^1.0.12 + version: 1.0.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-email/render': + specifier: ^2.1.0 + version: 2.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@repo/db': specifier: workspace:* version: link:../../packages/db @@ -44,6 +50,9 @@ importers: date-fns-tz: specifier: ^3.2.0 version: 3.2.0(date-fns@4.4.0) + ics: + specifier: ^3.12.0 + version: 3.12.0 lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.4) @@ -71,6 +80,9 @@ importers: react-hook-form: specifier: ^7.84.0 version: 7.84.0(react@19.2.4) + resend: + specifier: ^6.22.0 + version: 6.22.0(@react-email/render@2.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) shadcn: specifier: ^4.16.1 version: 4.16.1(typescript@5.9.3) @@ -1312,6 +1324,192 @@ packages: '@types/react': optional: true + '@react-email/body@0.3.0': + resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/components@1.0.12': + resolution: {integrity: sha512-tH18JhPDWgE+3jnYkzyB6ZrZdfNnEsFe4PwmuXmlOw4NGIysP8wPY5aXZg++pTG9qUabXg1nzX/FGHGkObH8xQ==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/render@2.0.6': + resolution: {integrity: sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog==} + 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/render@2.1.0': + resolution: {integrity: sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==} + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/tailwind@2.0.7': + resolution: {integrity: sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==} + engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + 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'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@rolldown/binding-android-arm64@1.2.2': resolution: {integrity: sha512-l7x215OGvo1s52JWmR8U/DAVzEDWBCIbTm28aeJV/WDTSHgcKXaZTuBT0hJMs5NggilfJTW3clZVvd24yfKJxA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1408,10 +1606,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==} @@ -2512,6 +2716,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@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -2558,6 +2775,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -2824,6 +3045,9 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-uri@3.1.4: resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} @@ -3042,6 +3266,16 @@ 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'} + + html5parser@3.0.0: + resolution: {integrity: sha512-iNpSopa+4YHX50UOk825tBy7MghmXHo/ZpLskBYN0kAr1xhH8GlIMk5bLRXcZlfP3AnLUcSuFMu8C4MdOUxA8A==} + + 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'} @@ -3058,6 +3292,9 @@ packages: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} + ics@3.12.0: + resolution: {integrity: sha512-BBPiZ/TbUyZrxui7SHKGOa/n2HUxaW9ucn7cw47NW9OSKe5tItYxIdN7Uczti5nKURmfwVvYi2aLigCJGH1+cA==} + icu-minify@4.13.4: resolution: {integrity: sha512-yK6HyPLGlQjqm8fTKtnBpM77z7vl7JdDBN2EXLvmgAu/b7XaOHWZb73M3ISl9ahBTehBv7RYeqqWSHfk1v2YcA==} @@ -3397,6 +3634,9 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + 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'} @@ -3599,6 +3839,11 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + 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'} @@ -3878,6 +4123,9 @@ packages: parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + 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'} @@ -3910,6 +4158,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -3942,6 +4193,9 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postal-mime@2.7.5: + resolution: {integrity: sha512-GNEXKvWFQnbgO5NlrGzVa0FmWzBZ24PersAWErttSg1Hjpf0ATxTwS5DOMGaOpTG6bUh5cTr7xi0jAD942wCJA==} + postcss-selector-parser@7.1.4: resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} engines: {node: '>=4'} @@ -3978,6 +4232,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -3999,6 +4258,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'} @@ -4009,6 +4272,9 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4099,6 +4365,15 @@ packages: reselect@5.2.0: resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + resend@6.22.0: + resolution: {integrity: sha512-dP1jyl0n1Dx7GYDb0bcsbv9Yx0oBRLhUtSyNFRIOsSzV9MvCVbua/3WP6YUP2JptAU5BciEWGInV+hFjUuYLUw==} + 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'} @@ -4146,6 +4421,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + runes2@1.1.4: + resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==} + safe-array-concat@1.1.4: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} @@ -4172,6 +4450,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4271,6 +4552,9 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -4388,6 +4672,9 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -4421,6 +4708,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + tough-cookie@6.0.2: resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} @@ -4464,6 +4754,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} @@ -4751,6 +5045,9 @@ packages: resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} engines: {node: '>=18'} + yup@1.7.1: + resolution: {integrity: sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==} + zeptomatch@2.1.0: resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==} @@ -5265,7 +5562,7 @@ snapshots: dependencies: hono: 4.12.33 - '@hookform/resolvers@5.7.1(@standard-schema/spec@1.1.0)(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(effect@3.20.0)(react-hook-form@7.84.0(react@19.2.4))(valibot@1.4.2(typescript@5.9.3))(zod@4.4.3)': + '@hookform/resolvers@5.7.1(@standard-schema/spec@1.1.0)(ajv-formats@3.0.1(ajv@8.20.0))(ajv@8.20.0)(effect@3.20.0)(react-hook-form@7.84.0(react@19.2.4))(valibot@1.4.2(typescript@5.9.3))(yup@1.7.1)(zod@4.4.3)': dependencies: '@standard-schema/utils': 0.3.0 react-hook-form: 7.84.0(react@19.2.4) @@ -5275,6 +5572,7 @@ snapshots: ajv-formats: 3.0.1(ajv@8.20.0) effect: 3.20.0 valibot: 1.4.2(typescript@5.9.3) + yup: 1.7.1 zod: 4.4.3 '@humanfs/core@0.19.2': @@ -5734,6 +6032,139 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + '@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.12(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.6(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.7(@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.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + html-to-text: 9.0.5 + prettier: 3.9.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@react-email/render@2.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + entities: 4.5.0 + html-to-text: 9.0.5 + html5parser: 3.0.0 + prettier: 3.9.6 + 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.7(@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.3.3 + 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/binding-android-arm64@1.2.2': optional: true @@ -5784,8 +6215,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': {} @@ -6853,6 +7291,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@6.0.1: dependencies: is-obj: 2.0.0 @@ -6894,6 +7350,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + entities@7.0.1: {} entities@8.0.0: {} @@ -7377,6 +7835,8 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 + fast-sha256@1.3.0: {} + fast-uri@3.1.4: {} fastq@1.20.1: @@ -7599,6 +8059,23 @@ 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 + + html5parser@3.0.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 @@ -7615,6 +8092,12 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ics@3.12.0: + dependencies: + nanoid: 3.3.16 + runes2: 1.1.4 + yup: 1.7.1 + icu-minify@4.13.4: dependencies: '@formatjs/icu-messageformat-parser': 3.5.16 @@ -7926,6 +8409,8 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -8083,6 +8568,8 @@ snapshots: dependencies: semver: 7.8.5 + marked@15.0.12: {} + math-intrinsics@1.1.0: {} mdn-data@2.27.1: {} @@ -8368,6 +8855,11 @@ snapshots: dependencies: entities: 8.0.0 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} path-browserify@1.0.1: {} @@ -8386,6 +8878,8 @@ snapshots: pathe@2.0.3: {} + peberminta@0.9.0: {} + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} @@ -8410,6 +8904,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + postal-mime@2.7.5: {} + postcss-selector-parser@7.1.4: dependencies: cssesc: 3.0.0 @@ -8441,6 +8937,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.9.6: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -8468,6 +8966,8 @@ snapshots: - react - react-dom + prismjs@1.30.0: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -8485,6 +8985,8 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + property-expr@2.0.6: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -8579,6 +9081,13 @@ snapshots: reselect@5.2.0: {} + resend@6.22.0(@react-email/render@2.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)): + dependencies: + postal-mime: 2.7.5 + standardwebhooks: 1.0.0 + optionalDependencies: + '@react-email/render': 2.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -8641,6 +9150,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + runes2@1.1.4: {} + safe-array-concat@1.1.4: dependencies: call-bind: 1.0.9 @@ -8672,6 +9183,10 @@ snapshots: scheduler@0.27.0: {} + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@6.3.1: {} semver@7.8.5: {} @@ -8851,6 +9366,11 @@ snapshots: stackback@0.0.2: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + statuses@2.0.2: {} std-env@3.10.0: {} @@ -8970,6 +9490,8 @@ snapshots: tapable@2.3.3: {} + tiny-case@1.0.3: {} + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -8995,6 +9517,8 @@ snapshots: toidentifier@1.0.1: {} + toposort@2.0.2: {} + tough-cookie@6.0.2: dependencies: tldts: 7.4.10 @@ -9048,6 +9572,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@2.19.0: {} + type-is@2.1.0: dependencies: content-type: 2.0.0 @@ -9329,6 +9855,13 @@ snapshots: yoctocolors@2.2.0: {} + yup@1.7.1: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + zeptomatch@2.1.0: dependencies: grammex: 3.1.13