fix email sender all flow
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user