email service and template using resend

This commit is contained in:
2026-05-18 20:47:05 +07:00
parent f0ce22bbb8
commit bf5c97c442
16 changed files with 1152 additions and 2 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ async function getJobSummary(jobName: string): Promise<JobSummary> {
}
// Daftar cron yang dipantau. Tambah entry baru saat menambah cron route handler.
const TRACKED_JOBS = ["auto-complete-trips"] as const;
const TRACKED_JOBS = ["auto-complete-trips", "process-email-jobs"] as const;
function healthOf(summary: JobSummary): "ok" | "stale" | "failed" {
if (summary.lastRun?.status === "FAILED") return "failed";
+36
View File
@@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from "next/server";
import { emailService } from "@/lib/email/send";
import { runCron } from "@/lib/cron-runner";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* Cron — proses retry queue email (jobs status PENDING/FAILED dengan
* attempts<5 dan scheduledAt sudah lewat).
*
* Trigger setiap 5 menit via system crontab — lihat docs/CRON_SETUP.md.
* Header wajib: `Authorization: Bearer ${CRON_SECRET}`.
*/
export async function GET(req: NextRequest) {
const secret = process.env.CRON_SECRET;
if (!secret) {
return NextResponse.json(
{ error: "Server misconfigured (CRON_SECRET)" },
{ status: 500 }
);
}
const authHeader = req.headers.get("authorization");
if (authHeader !== `Bearer ${secret}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const outcome = await runCron("process-email-jobs", async () => {
return emailService.processQueue(50);
});
if (!outcome.ok) {
return NextResponse.json({ error: outcome.error }, { status: 500 });
}
return NextResponse.json({ ok: true, ...outcome.payload });
}