Build AI Chief of Staff MVP: auth, mailbox sync, briefings, draft replies
Scaffolds a Next.js app with email/password auth, encrypted IMAP/SMTP/CalDAV mailbox connections, a background sync job, Claude-generated daily briefings, and an approve-before-send draft reply flow. Includes a clean design system (light/dark) and deployment config (systemd unit, credential rotation script). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY
This commit is contained in:
1 parent
facd4d71bd
commit
bcf0e86618
65 files changed
+13413
-1
No files matched your search
@@ -0,0 +1,55 @@
|
||||
export const runtime = "nodejs";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { getCurrentSession } from "@/lib/auth/session";
|
||||
import { db } from "@/lib/db/client";
|
||||
import { emails, emailDrafts } from "@/lib/db/schema";
|
||||
import { generateDraftReply } from "@/lib/ai/draftReply";
|
||||
import { BRIEFING_MODEL } from "@/lib/ai/client";
|
||||
|
||||
const bodySchema = z.object({ emailId: z.string().min(1) });
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await getCurrentSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsed = bodySchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
const email = await db.query.emails.findFirst({
|
||||
where: and(eq(emails.id, parsed.data.emailId), eq(emails.userId, session.user.id)),
|
||||
});
|
||||
if (!email) {
|
||||
return NextResponse.json({ error: "Email not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const draft = await generateDraftReply({
|
||||
from: email.fromAddress,
|
||||
subject: email.subject,
|
||||
bodyText: email.bodyText ?? email.snippet ?? "",
|
||||
});
|
||||
|
||||
const [row] = await db
|
||||
.insert(emailDrafts)
|
||||
.values({
|
||||
userId: session.user.id,
|
||||
sourceEmailId: email.id,
|
||||
generatedSubject: draft.subject,
|
||||
generatedBody: draft.body,
|
||||
modelUsed: BRIEFING_MODEL,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return NextResponse.json({ draft: row });
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user