fix email sender all flow

This commit is contained in:
2026-05-20 15:25:32 +07:00
parent 306396ae43
commit cb03967deb
20 changed files with 1450 additions and 62 deletions
+121
View File
@@ -77,6 +77,7 @@ export const emailService = {
to: input.to,
template: input.template.template,
subject: rendered.subject,
html: rendered.html,
providerMessageId: result.data?.id ?? null,
},
});
@@ -162,6 +163,7 @@ export const emailService = {
to: job.to,
template: job.template,
subject: job.subject,
html: job.html,
providerMessageId: result.data?.id ?? null,
},
}),
@@ -188,6 +190,125 @@ export const emailService = {
return { picked: jobs.length, succeeded, failed };
},
/**
* Admin "Retry now" untuk satu EmailJob — kirim ulang langsung tanpa
* menunggu cron. Idempotent: kalau email sudah tercatat terkirim, job
* ditandai SUCCESS tanpa kirim ulang.
*/
async retryJob(jobId: string): Promise<{ ok: boolean; error?: string }> {
const job = await prisma.emailJob.findUnique({ where: { id: jobId } });
if (!job) return { ok: false, error: "Email job tidak ditemukan" };
const alreadySent = await prisma.emailSent.findUnique({
where: { idempotencyKey: job.idempotencyKey },
select: { id: true },
});
if (alreadySent) {
await prisma.emailJob.update({
where: { id: jobId },
data: { status: "SUCCESS", lastAttemptAt: new Date() },
});
return { ok: true };
}
const resend = getResend();
if (!resend) return { ok: false, error: "RESEND_API_KEY belum di-set" };
const now = new Date();
try {
const result = await resend.emails.send({
from: emailFrom(),
to: job.to,
subject: job.subject,
html: job.html,
});
if (result.error) throw new Error(result.error.message ?? "Resend failed");
await prisma.$transaction([
prisma.emailSent.create({
data: {
idempotencyKey: job.idempotencyKey,
to: job.to,
template: job.template,
subject: job.subject,
html: job.html,
providerMessageId: result.data?.id ?? null,
},
}),
prisma.emailJob.update({
where: { id: jobId },
data: {
status: "SUCCESS",
attempts: job.attempts + 1,
lastAttemptAt: now,
},
}),
]);
return { ok: true };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
await prisma.emailJob.update({
where: { id: jobId },
data: {
status: "FAILED",
attempts: job.attempts + 1,
lastAttemptAt: now,
lastError: message,
},
});
return { ok: false, error: message };
}
},
/**
* Admin "Resend" untuk EmailSent yang sudah pernah terkirim — mis. user
* lapor tidak menerima. Pakai idempotencyKey turunan supaya tidak bentrok
* dengan email asli. Butuh `html` tersimpan (row lama tidak bisa di-resend).
*/
async resendEmail(
emailSentId: string
): Promise<{ ok: boolean; error?: string }> {
const original = await prisma.emailSent.findUnique({
where: { id: emailSentId },
});
if (!original) return { ok: false, error: "Email tidak ditemukan" };
if (!original.html) {
return {
ok: false,
error:
"Body email lama tidak tersimpan — tidak bisa di-resend (dikirim sebelum fitur ini ada).",
};
}
const resend = getResend();
if (!resend) return { ok: false, error: "RESEND_API_KEY belum di-set" };
try {
const result = await resend.emails.send({
from: emailFrom(),
to: original.to,
subject: original.subject,
html: original.html,
});
if (result.error) throw new Error(result.error.message ?? "Resend failed");
await prisma.emailSent.create({
data: {
idempotencyKey: `${original.idempotencyKey}#resend-${Date.now()}`,
to: original.to,
template: original.template,
subject: original.subject,
html: original.html,
providerMessageId: result.data?.id ?? null,
},
});
return { ok: true };
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
};
}
},
};
async function enqueueJob(