import { getTransporter, emailConfigured, FROM_EMAIL, APP_NAME } from "./client" import { BRAND, emailShell, detailTable, statusBadge, paragraph, escapeHtml, htmlToText, } from "./layout" // Re-exported so existing importers (`@/lib/email/send`) keep working. export { escapeHtml } interface SendEmailOptions { to: string subject: string html: string /** Optional plain-text part. Auto-derived from `html` when omitted. */ text?: string from?: string } export async function sendEmail({ to, subject, html, text, from }: SendEmailOptions) { if (!emailConfigured()) { console.warn(`[email] SMTP not configured — skipping "${subject}" to ${to}`) return { success: false, error: "email not configured" } } try { const info = await getTransporter().sendMail({ from: from ?? `${APP_NAME} <${FROM_EMAIL}>`, to, subject, html, // A plain-text alternative improves inbox placement and gives clients // that can't render HTML something clean to show. text: text ?? htmlToText(html), }) return { success: true, id: info.messageId } } catch (error) { console.error("Email send failed:", error) return { success: false, error } } } // ── Email templates ────────────────────────────────────────────── export function rentDueReminderHtml({ tenantName, propertyName, unitNumber, amount, dueDate, paymentLink, }: { tenantName: string propertyName: string unitNumber: string amount: string dueDate: string paymentLink?: string }) { return emailShell({ preheader: `Your rent of ${amount} is due on ${dueDate}.`, eyebrow: "Rent reminder", accent: BRAND.indigo, title: "Your rent is due soon", intro: `Hi ${escapeHtml(tenantName)}, this is a friendly reminder about your upcoming rent payment.`, body: detailTable([ { label: "Property", value: `${propertyName} — Unit ${unitNumber}` }, { label: "Amount due", value: amount, accent: true }, { label: "Due date", value: dueDate }, ]), button: paymentLink ? { href: paymentLink, label: "Pay rent now" } : undefined, footerNote: paymentLink ? "If the button doesn't work, contact your landlord for alternative payment options." : "Please arrange payment before the due date. Reach out to your landlord with any questions.", }) } export function rentOverdueHtml({ tenantName, propertyName, unitNumber, amount, dueDate, }: { tenantName: string propertyName: string unitNumber: string amount: string dueDate: string }) { return emailShell({ preheader: `Your rent payment of ${amount} is now overdue.`, eyebrow: "Action needed", accent: BRAND.red, title: "Your rent is overdue", intro: `Hi ${escapeHtml(tenantName)}, our records show the payment below hasn't been received yet.`, body: detailTable( [ { label: "Property", value: `${propertyName} — Unit ${unitNumber}` }, { label: "Amount due", value: amount, accent: true }, { label: "Was due", value: dueDate, accent: true }, ], BRAND.red ) + paragraph( "Please make payment as soon as possible to avoid any late fees. If you've already paid, you can disregard this notice." ), }) } export function leaseExpiryHtml({ tenantName, propertyName, unitNumber, leaseEnd, daysLeft, }: { tenantName: string propertyName: string unitNumber: string leaseEnd: string daysLeft: number }) { return emailShell({ preheader: `Your lease ends on ${leaseEnd} (${daysLeft} days away).`, eyebrow: "Lease update", accent: BRAND.amber, title: "Your lease is expiring soon", intro: `Hi ${escapeHtml(tenantName)}, your current lease is coming to an end.`, body: detailTable( [ { label: "Property", value: `${propertyName} — Unit ${unitNumber}` }, { label: "Lease ends", value: leaseEnd, accent: true }, { label: "Time remaining", value: `${daysLeft} days` }, ], BRAND.amber ) + paragraph("Please contact your landlord to discuss renewal options or next steps."), }) } export function maintenanceUpdateHtml({ tenantName, title, status, resolutionNotes, }: { tenantName: string title: string status: string resolutionNotes?: string }) { const prettyStatus = status.replace(/_/g, " ") const fontStack = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif" return emailShell({ preheader: `Your maintenance request is now "${prettyStatus}".`, eyebrow: "Maintenance", accent: BRAND.indigo, title: "Update on your maintenance request", intro: `Hi ${escapeHtml(tenantName)}, there's an update on your request.`, body: `

${escapeHtml(title)}

${statusBadge(prettyStatus, BRAND.indigo)}
` + (resolutionNotes ? paragraph(escapeHtml(resolutionNotes), { muted: true }) : ""), }) } export function resetPasswordHtml(url: string) { return emailShell({ preheader: "Reset your Property Management Network password.", eyebrow: "Security", title: "Reset your password", intro: "We received a request to reset your password. Click the button below to choose a new one — this link will expire shortly for your security.", button: { href: url, label: "Reset password" }, footerNote: "If you didn't request a password reset, you can safely ignore this email — your password won't change.", }) } export function verifyEmailHtml(url: string) { return emailShell({ preheader: "Confirm your email to finish setting up your account.", eyebrow: "Welcome", title: "Verify your email address", intro: "Thanks for signing up! Please confirm your email address to finish setting up your account.", button: { href: url, label: "Verify email" }, footerNote: "If you didn't create an account, you can safely ignore this email.", }) } export function teamInviteHtml({ inviterName, inviteUrl, role, }: { inviterName: string inviteUrl: string role: "member" | "viewer" }) { const roleLabel = role === "viewer" ? "view" : "manage" return emailShell({ preheader: `${inviterName} invited you to their property portfolio.`, eyebrow: "Team invitation", title: "You've been invited to a team", intro: `${escapeHtml(inviterName)} has invited you to ${escapeHtml(roleLabel)} their property portfolio on ${escapeHtml(APP_NAME)}.`, button: { href: inviteUrl, label: "Accept invitation" }, footerNote: "If you don't have an account yet, you'll be asked to sign in or sign up first.", }) } export function accountDeletionRequestedHtml({ name, scheduledDate, graceDays, }: { name: string scheduledDate: string graceDays: number }) { return emailShell({ preheader: `Your account is scheduled for permanent deletion on ${scheduledDate}.`, eyebrow: "Account deletion", accent: BRAND.red, title: "Your account deletion is scheduled", intro: `Hi ${escapeHtml(name)}, we received your request to delete your account and all associated data.`, body: detailTable( [ { label: "Deletion date", value: scheduledDate, accent: true }, { label: "Grace period", value: `${graceDays} days` }, ], BRAND.red ) + paragraph( "Until then your account stays fully usable, and you can cancel the deletion at any time from Settings → Privacy & Data. After the deletion date, ALL your properties, tenants, payments, documents, and uploaded files are permanently erased — this cannot be undone." ), footerNote: "If you did not request this, sign in and cancel the deletion immediately, then change your password.", }) } export function accountDeletionCompletedHtml() { return emailShell({ preheader: "Your account and personal data have been permanently deleted.", eyebrow: "Account deletion", title: "Your account has been deleted", intro: "As requested, your account and the personal data associated with it have been permanently deleted from our systems.", body: paragraph( "Financial records we are legally required to retain (for example, invoices held by our payment processor) are kept only for as long as the law requires. Everything else — your properties, tenants, documents, and uploaded files — is gone." ), footerNote: "Thanks for having used Property Management Network. You're welcome back anytime.", }) } export function followUpHtml(message: string) { return emailShell({ preheader: message.slice(0, 140), accent: BRAND.indigo, title: "A quick note from your landlord", body: paragraph(escapeHtml(message).replace(/\n/g, "
")), footerNote: "Sent automatically by your landlord's follow-up system.", }) } export function paymentLinkHtml({ tenantName, amount, dueDate, propertyLabel, senderName, paymentLink, }: { tenantName: string amount: string dueDate: string propertyLabel: string senderName: string paymentLink?: string }) { return emailShell({ preheader: `Rent payment of ${amount} due ${dueDate}.`, eyebrow: "Rent payment", accent: BRAND.indigo, title: "Your rent payment is due", intro: `Hi ${escapeHtml(tenantName)}, here are the details for your upcoming rent payment.`, body: detailTable([ { label: "Property", value: propertyLabel }, { label: "Amount due", value: amount, accent: true }, { label: "Due date", value: dueDate }, ]), button: paymentLink ? { href: paymentLink, label: "Pay rent now" } : undefined, footerNote: `Sent by ${escapeHtml(senderName)} via ${escapeHtml(APP_NAME)}.`, }) }