diff --git a/README.md b/README.md index fb11ee2cc8..28c0ec2c10 100644 --- a/README.md +++ b/README.md @@ -68,13 +68,12 @@ We are working on the documentation to get started with Midday for local develop ### Services - Trigger.dev (background jobs) -- Resend (email) +- Resend (Transactional & Marketing) - Novu (notifications) - Github Actions (CI/CD) - GoCardLess (Bank connection EU) - Plaid (Bank connection in Canada and US) - Teller (Bank connection in the US) -- Loops (Marketing email) - OpenPanel (Events and Analytics) - Dub (Short URLs) - Polar (Payment processing) diff --git a/apps/dashboard/.env-example b/apps/dashboard/.env-example index 7a1126e62f..d2af2b7779 100644 --- a/apps/dashboard/.env-example +++ b/apps/dashboard/.env-example @@ -6,10 +6,7 @@ SUPABASE_SERVICE_KEY= # Resend RESEND_API_KEY= - -# Loops -LOOPS_ENDPOINT= -LOOPS_API_KEY= +RESEND_AUDIENCE_ID= # GoCardLess GOCARDLESS_SECRET_ID= @@ -60,10 +57,6 @@ MISTRAL_API_KEY= NEXT_PUBLIC_OPENPANEL_CLIENT_ID= OPENPANEL_SECRET_KEY= -# Engine -ENGINE_API_ENDPOINT=http://localhost:3002 -ENGINE_API_SECRET=secret - # Webhook WEBHOOK_SECRET_KEY=6c369443-1a88-444e-b459-7e662c1fff9e @@ -75,7 +68,7 @@ SENTRY_PROJECT= # Engine MIDDAY_ENGINE_API_KEY=secret -MIDDAY_BASE_URL=http://localhost:3002 +NEXT_PUBLIC_ENGINE_API_URL=http://localhost:3002 # Azure AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT= diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md index 75b67d4b5f..3a38f9b3eb 100644 --- a/apps/dashboard/README.md +++ b/apps/dashboard/README.md @@ -1 +1 @@ -## Dashboard +## Dashboard \ No newline at end of file diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index a6dceb47c2..81090e2605 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -50,7 +50,6 @@ "framer-motion": "^11.12.0", "geist": "^1.3.1", "little-date": "^1.0.0", - "loops": "1.0.1", "lottie-react": "^2.4.0", "ms": "^2.1.3", "next": "14.2.1", @@ -70,7 +69,7 @@ "react-plaid-link": "^3.6.0", "react-use-draggable-scroll": "^0.4.7", "recharts": "^2.12.7", - "resend": "^3.5.0", + "resend": "^4.0.1", "sharp": "^0.33.5", "tus-js-client": "4.1.0", "use-long-press": "^3.2.0", diff --git a/apps/dashboard/src/actions/delete-user-action.ts b/apps/dashboard/src/actions/delete-user-action.ts index 30254eba3b..2615957769 100644 --- a/apps/dashboard/src/actions/delete-user-action.ts +++ b/apps/dashboard/src/actions/delete-user-action.ts @@ -1,15 +1,13 @@ "use server"; +import { resend } from "@/utils/resend"; import { LogEvents } from "@midday/events/events"; import { setupAnalytics } from "@midday/events/server"; import { getUser } from "@midday/supabase/cached-queries"; import { deleteUser } from "@midday/supabase/mutations"; import { createClient } from "@midday/supabase/server"; -import { LoopsClient } from "loops"; import { redirect } from "next/navigation"; -const loops = new LoopsClient(process.env.LOOPS_API_KEY!); - export const deleteUserAction = async () => { const supabase = createClient(); const user = await getUser(); @@ -30,7 +28,10 @@ export const deleteUserAction = async () => { const userId = await deleteUser(supabase); - await loops.deleteContact({ userId }); + await resend.contacts.remove({ + email: user.data?.email!, + audienceId: process.env.RESEND_AUDIENCE_ID!, + }); const analytics = await setupAnalytics({ userId, diff --git a/apps/dashboard/src/actions/schema.ts b/apps/dashboard/src/actions/schema.ts index afe2d85740..a607397066 100644 --- a/apps/dashboard/src/actions/schema.ts +++ b/apps/dashboard/src/actions/schema.ts @@ -70,7 +70,6 @@ export type UpdateTeamFormValues = z.infer; export const subscribeSchema = z.object({ email: z.string().email(), - userGroup: z.string(), }); export const deleteBankAccountSchema = z.object({ diff --git a/apps/dashboard/src/actions/subscribe-action.ts b/apps/dashboard/src/actions/subscribe-action.ts index 51256cbae3..eff42df905 100644 --- a/apps/dashboard/src/actions/subscribe-action.ts +++ b/apps/dashboard/src/actions/subscribe-action.ts @@ -1,5 +1,6 @@ "use server"; +import { resend } from "@/utils/resend"; import { authActionClient } from "./safe-action"; import { subscribeSchema } from "./schema"; @@ -8,20 +9,9 @@ export const subscribeAction = authActionClient .metadata({ name: "subscribe", }) - .action(async ({ parsedInput: { email, userGroup } }) => { - const res = await fetch( - "https://app.loops.so/api/newsletter-form/clna1p09j00d3l60og56gj3u1", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email, - userGroup, - }), - }, - ); - - return res.json(); + .action(async ({ parsedInput: { email } }) => { + return resend.contacts.create({ + email, + audienceId: process.env.RESEND_AUDIENCE_ID!, + }); }); diff --git a/apps/dashboard/src/app/api/webhook/registered/route.ts b/apps/dashboard/src/app/api/webhook/registered/route.ts index 5bb70633e6..86206b0f52 100644 --- a/apps/dashboard/src/app/api/webhook/registered/route.ts +++ b/apps/dashboard/src/app/api/webhook/registered/route.ts @@ -1,21 +1,17 @@ import * as crypto from "node:crypto"; import { env } from "@/env.mjs"; import { logger } from "@/utils/logger"; +import { resend } from "@/utils/resend"; import WelcomeEmail from "@midday/email/emails/welcome"; import { LogEvents } from "@midday/events/events"; import { setupAnalytics } from "@midday/events/server"; import { render } from "@react-email/render"; -import { LoopsClient } from "loops"; import { nanoid } from "nanoid"; import { headers } from "next/headers"; import { NextResponse } from "next/server"; -import { Resend } from "resend"; export const dynamic = "force-dynamic"; -const loops = new LoopsClient(env.LOOPS_API_KEY); -const resend = new Resend(env.RESEND_API_KEY); - // NOTE: This is trigger from supabase database webhook export async function POST(req: Request) { const text = await req.clone().text(); @@ -74,34 +70,17 @@ export async function POST(req: Request) { } try { - const found = await loops.findContact(email); const [firstName, lastName] = fullName?.split(" ") ?? []; - if (found.length > 0) { - const userId = found?.at(0)?.id; - - if (!userId) { - return null; - } - - await loops.updateContact(email, { - userId, - userGroup: "registered", - firstName, - lastName, - }); - } else { - await loops.createContact(email, { - userId: body.record.id, - userGroup: "registered", - firstName, - lastName, - }); - } + await resend.contacts.create({ + email, + firstName, + lastName, + unsubscribed: false, + audienceId: env.RESEND_AUDIENCE_ID, + }); } catch (error) { - const message = error instanceof Error ? error.message : "Unknown error"; - - logger(message); + logger(error as string); } return NextResponse.json({ success: true }); diff --git a/apps/dashboard/src/env.mjs b/apps/dashboard/src/env.mjs index c84727beeb..d033e4335d 100644 --- a/apps/dashboard/src/env.mjs +++ b/apps/dashboard/src/env.mjs @@ -19,12 +19,11 @@ export const env = createEnv({ SUPABASE_SERVICE_KEY: z.string(), UPSTASH_REDIS_REST_TOKEN: z.string(), UPSTASH_REDIS_REST_URL: z.string(), - LOOPS_ENDPOINT: z.string(), - LOOPS_API_KEY: z.string(), GOCARDLESS_SECRET_ID: z.string(), GOCARDLESS_SECRET_KEY: z.string(), NOVU_API_KEY: z.string(), RESEND_API_KEY: z.string(), + RESEND_AUDIENCE_ID: z.string(), OPENPANEL_SECRET_KEY: z.string(), MIDDAY_ENGINE_API_KEY: z.string(), MIDDAY_CACHE_API_SECRET: z.string(), @@ -61,9 +60,8 @@ export const env = createEnv({ NEXT_PUBLIC_TELLER_ENVIRONMENT: process.env.NEXT_PUBLIC_TELLER_ENVIRONMENT, NEXT_PUBLIC_PLAID_ENVIRONMENT: process.env.NEXT_PUBLIC_PLAID_ENVIRONMENT, RESEND_API_KEY: process.env.RESEND_API_KEY, + RESEND_AUDIENCE_ID: process.env.RESEND_AUDIENCE_ID, PORT: process.env.PORT, - LOOPS_ENDPOINT: process.env.LOOPS_ENDPOINT, - LOOPS_API_KEY: process.env.LOOPS_API_KEY, GOCARDLESS_SECRET_ID: process.env.GOCARDLESS_SECRET_ID, GOCARDLESS_SECRET_KEY: process.env.GOCARDLESS_SECRET_KEY, NOVU_API_KEY: process.env.NOVU_API_KEY, diff --git a/apps/dashboard/src/utils/engine.ts b/apps/dashboard/src/utils/engine.ts deleted file mode 100644 index af70d5b089..0000000000 --- a/apps/dashboard/src/utils/engine.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Midday from "@midday-ai/engine"; - -export const engine = new Midday({ - environment: process.env.MIDDAY_ENGINE_ENVIRONMENT as - | "production" - | "staging" - | "development" - | undefined, -}); diff --git a/apps/website/.env-template b/apps/website/.env-template index a971fefdf8..a54e4c1856 100644 --- a/apps/website/.env-template +++ b/apps/website/.env-template @@ -1,3 +1,5 @@ UPSTASH_REDIS_REST_TOKEN= UPSTASH_REDIS_REST_URL= -NEXT_PUBLIC_OPENPANEL_CLIENT_ID= \ No newline at end of file +NEXT_PUBLIC_OPENPANEL_CLIENT_ID= +RESEND_API_KEY= +RESEND_AUDIENCE_ID= \ No newline at end of file diff --git a/apps/website/package.json b/apps/website/package.json index 725318b279..7e72e74545 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -28,6 +28,7 @@ "react-dom": "19.0.0-rc-66855b96-20241106", "react-hls-player": "^3.0.7", "react-use-draggable-scroll": "^0.4.7", + "resend": "^4.0.1", "server-only": "^0.0.1", "sharp": "^0.33.5", "sugar-high": "^0.7.5" diff --git a/apps/website/src/actions/subscribe-action.ts b/apps/website/src/actions/subscribe-action.ts index 999376bff0..952fef4473 100644 --- a/apps/website/src/actions/subscribe-action.ts +++ b/apps/website/src/actions/subscribe-action.ts @@ -1,27 +1,12 @@ "use server"; -import { getCountryCode } from "@midday/location"; +import { resend } from "@/utils/resend"; -export async function subscribeAction(formData: FormData, userGroup: string) { +export async function subscribeAction(formData: FormData) { const email = formData.get("email") as string; - const country = await getCountryCode(); - const res = await fetch( - "https://app.loops.so/api/newsletter-form/clna1p09j00d3l60og56gj3u1", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email, - userGroup, - country, - }), - }, - ); - - const json = await res.json(); - - return json; + return resend.contacts.create({ + email, + audienceId: process.env.RESEND_AUDIENCE_ID!, + }); } diff --git a/apps/website/src/app/engine/page.tsx b/apps/website/src/app/engine/page.tsx index aaf84dd7cb..489102044f 100644 --- a/apps/website/src/app/engine/page.tsx +++ b/apps/website/src/app/engine/page.tsx @@ -31,7 +31,7 @@ export default function Page() {

- +

diff --git a/apps/website/src/components/footer.tsx b/apps/website/src/components/footer.tsx index 326f8f1d00..2903fc4d81 100644 --- a/apps/website/src/components/footer.tsx +++ b/apps/website/src/components/footer.tsx @@ -103,7 +103,7 @@ export function Footer() {

- +
diff --git a/apps/website/src/components/subscribe-input.tsx b/apps/website/src/components/subscribe-input.tsx index 6f3a730724..6ce7c76fcb 100644 --- a/apps/website/src/components/subscribe-input.tsx +++ b/apps/website/src/components/subscribe-input.tsx @@ -26,11 +26,7 @@ function SubmitButton() { ); } -type Props = { - group: string; -}; - -export function SubscribeInput({ group }: Props) { +export function SubscribeInput() { const [isSubmitted, setSubmitted] = useState(false); return ( @@ -57,7 +53,7 @@ export function SubscribeInput({ group }: Props) {
{ setSubmitted(true); - await subscribeAction(formData, group); + await subscribeAction(formData); setTimeout(() => { setSubmitted(false); diff --git a/apps/website/src/utils/resend.ts b/apps/website/src/utils/resend.ts new file mode 100644 index 0000000000..295c033026 --- /dev/null +++ b/apps/website/src/utils/resend.ts @@ -0,0 +1,3 @@ +import { Resend } from "resend"; + +export const resend = new Resend(process.env.RESEND_API_KEY!); diff --git a/bun.lockb b/bun.lockb index 8a9a087eb8..66c491a806 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9368dff027..568ac078a8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "turbo": "2.3.3", "typescript": "^5.7.2" }, - "packageManager": "bun@1.1.27", + "packageManager": "bun@1.1.38", "resolutions": { "jackspeak": "2.1.1" } diff --git a/turbo.json b/turbo.json index 0e6395a1cf..645e2a27b2 100644 --- a/turbo.json +++ b/turbo.json @@ -11,8 +11,7 @@ "SUPABASE_SERVICE_KEY", "SUPABASE_API_KEY", "RESEND_API_KEY", - "LOOPS_ENDPOINT", - "LOOPS_API_KEY", + "RESEND_AUDIENCE_ID", "GOCARDLESS_SECRET_ID", "GOCARDLESS_SECRET_KEY", "UPSTASH_REDIS_REST_URL", @@ -22,7 +21,6 @@ "API_ROUTE_SECRET", "TELLER_CERTIFICATE", "TELLER_CERTIFICATE_PRIVATE_KEY", - "MIDDAY_ENGINE_ENVIRONMENT", "MIDDAY_ENGINE_API_KEY", "PLAID_CLIENT_ID", "PLAID_SECRET",