diff --git a/apps/site/src/lib/auth.ts b/apps/site/src/lib/auth.ts index f9f7bf78..80c112c1 100644 --- a/apps/site/src/lib/auth.ts +++ b/apps/site/src/lib/auth.ts @@ -2,7 +2,7 @@ import { env } from "cloudflare:workers"; import { mcp } from "@better-auth/mcp"; import { betterAuth } from "better-auth"; import { emailOTP, jwt, magicLink } from "better-auth/plugins"; -import { sendCodingCatEmail } from "@/lib/email"; +import { sendCodingCatAuthEmail } from "@/lib/email"; export const BETTER_AUTH_SECRET = (env as unknown as { BETTER_AUTH_SECRET?: string })?.BETTER_AUTH_SECRET || @@ -211,7 +211,7 @@ function buildPortalAuthPlugins() { `; - await sendCodingCatEmail({ + await sendCodingCatAuthEmail({ to: email, subject, text, @@ -260,7 +260,7 @@ function buildPortalAuthPlugins() { `; - await sendCodingCatEmail({ + await sendCodingCatAuthEmail({ to: email, subject, text, diff --git a/apps/site/src/lib/email.ts b/apps/site/src/lib/email.ts index 7b08e6b3..d4be69c8 100644 --- a/apps/site/src/lib/email.ts +++ b/apps/site/src/lib/email.ts @@ -424,6 +424,37 @@ export async function sendCodingCatEmailDetailed( }; } +/** + * Sign-in codes and magic links are the one exception to the disabled + * automated sending above: nobody can sign in without them. They go through + * Cloudflare Email Sending, which requires `from` on the onboarded + * codingcat.dev domain. + */ +export async function sendCodingCatAuthEmail(opts: { + to: string; + subject: string; + text: string; + html: string; +}): Promise { + const workerEnv = (env ?? {}) as unknown as { EMAIL?: SendEmailBinding }; + if (!workerEnv.EMAIL) { + throw new Error("EMAIL binding is not configured"); + } + try { + await workerEnv.EMAIL.send({ + to: opts.to, + from: { email: "noreply@codingcat.dev", name: "CodingCat.dev" }, + subject: opts.subject, + text: opts.text, + html: opts.html, + }); + } catch (error) { + const code = (error as { code?: string })?.code ?? "unknown"; + console.error(`Sign-in email failed: ${code}`); + throw error; + } +} + export async function sendCodingCatEmail( opts: CodingCatEmailOptions, ): Promise { diff --git a/apps/site/src/lib/google-calendar.ts b/apps/site/src/lib/google-calendar.ts index 31f15447..b21cec35 100644 --- a/apps/site/src/lib/google-calendar.ts +++ b/apps/site/src/lib/google-calendar.ts @@ -39,9 +39,7 @@ async function syncPrivateShadowCalendarBlocks(opts: { "Private Calendar Block synced from alex@codingcat.dev", `Room: ${studioOrMeet}`, `Guest: ${opts.guestName} (${opts.company})`, - ...(opts.portalUrl - ? [`Guest Studio Portal: ${opts.portalUrl}`] - : []), + ...(opts.portalUrl ? [`Guest Studio Portal: ${opts.portalUrl}`] : []), ].join("\n"), location: studioOrMeet, status: "confirmed", diff --git a/apps/site/src/pages/api/portal/notes.ts b/apps/site/src/pages/api/portal/notes.ts index d02e2ea9..cf543b30 100644 --- a/apps/site/src/pages/api/portal/notes.ts +++ b/apps/site/src/pages/api/portal/notes.ts @@ -851,7 +851,7 @@ export const POST: APIRoute = async ({ request, url }) => { headline: chatMessage && noteType === "call_note" ? `Call Notes & Next Steps for ${company} x CodingCat.dev` - : `Alex Patterson Updated Your Shared Run-of-Show & Notes`, + : "Alex Patterson Updated Your Shared Run-of-Show & Notes", introParagraph: chatMessage ? `I just posted new notes${newAttachments.length > 0 ? " and shared files" : ""} in your CodingCat.dev Studio Portal: "${chatMessage.slice(0, 280)}${chatMessage.length > 280 ? "..." : ""}". You can reply, drop files into the chat, or edit the shared Run-of-Show directly in your portal below.` : "I just updated our shared Pre-Call Notes and 5-Part Run-of-Show outline in your CodingCat.dev Studio Portal. Take a look below and feel free to edit or leave a comment directly in the portal!", @@ -913,7 +913,7 @@ export const POST: APIRoute = async ({ request, url }) => { ? `${resolvedAuthorName} Posted a Note in the Studio Portal` : `${resolvedAuthorName} Updated the Shared Run-of-Show`, introParagraph: isFeedback - ? `${resolvedAuthorName} (${verifiedEmail || guestEmail}) just submitted feedback on the CodingCat.dev Booking & Studio Portal${feedbackRating ? ` (Rating: ${feedbackRating}/5)` : ""}${feedbackTopics.length > 0 ? ` [${feedbackTopics.join(", ")}]` : ""}${newAttachments.length > 0 ? ` with an annotated screen capture synced to Sanity Media Library` : ""}.` + ? `${resolvedAuthorName} (${verifiedEmail || guestEmail}) just submitted feedback on the CodingCat.dev Booking & Studio Portal${feedbackRating ? ` (Rating: ${feedbackRating}/5)` : ""}${feedbackTopics.length > 0 ? ` [${feedbackTopics.join(", ")}]` : ""}${newAttachments.length > 0 ? " with an annotated screen capture synced to Sanity Media Library" : ""}.` : chatMessage ? `${resolvedAuthorName} (${verifiedEmail || guestEmail}) just posted a message${newAttachments.length > 0 ? ` and dropped ${newAttachments.length} attachment(s)` : ""} in the ${company} Studio Portal: "${chatMessage}".` : `${resolvedAuthorName} (${verifiedEmail || guestEmail}) just edited the shared Run-of-Show / Pre-Call Notes in the ${company} Studio Portal.`,