diff --git a/src/app/api/public/residential-inquiries/route.ts b/src/app/api/public/residential-inquiries/route.ts
index f6554c3a..7bb865f0 100644
--- a/src/app/api/public/residential-inquiries/route.ts
+++ b/src/app/api/public/residential-inquiries/route.ts
@@ -144,7 +144,7 @@ async function sendResidentialNotifications(args: {
const branding = await getBrandingShell(portId);
// Client confirmation
- const confirmation = residentialClientConfirmation(
+ const confirmation = await residentialClientConfirmation(
{
firstName: data.firstName,
contactEmail: 'sales@portnimara.com',
@@ -186,7 +186,7 @@ async function sendResidentialNotifications(args: {
return;
}
- const alert = residentialSalesAlert(
+ const alert = await residentialSalesAlert(
{
fullName: `${data.firstName} ${data.lastName}`.trim(),
email: data.email,
diff --git a/src/lib/email/templates/admin-email-change.ts b/src/lib/email/templates/admin-email-change.ts
deleted file mode 100644
index c03f4f90..00000000
--- a/src/lib/email/templates/admin-email-change.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
-
-interface AdminEmailChangeData {
- recipientName?: string;
- /** New address the user should sign in with from now on. */
- newEmail: string;
- /** Display name of the admin who initiated the change — surfaced so the
- * recipient knows who to follow up with. */
- changedByDisplayName?: string;
- /** Optional URL for the sign-in page. */
- loginUrl?: string;
- portName?: string;
-}
-
-interface RenderOpts {
- branding?: BrandingShell | null;
-}
-
-export function adminEmailChangeEmail(
- data: AdminEmailChangeData,
- overrides?: RenderOpts,
-): { subject: string; html: string; text: string } {
- const portName = data.portName ?? 'Port Nimara';
- const subject = `An administrator updated your ${portName} sign-in email`;
- const greeting = data.recipientName ? `Hello ${escapeHtml(data.recipientName)},` : 'Hello,';
- const accent = brandingPrimaryColor(overrides?.branding);
-
- const adminLine = data.changedByDisplayName
- ? `${escapeHtml(data.changedByDisplayName)} (an administrator)`
- : 'an administrator';
-
- const body = `
-
- Your sign-in email was changed
-
-
${greeting}
-
- ${adminLine} just updated the email address linked to your ${escapeHtml(
- portName,
- )} account. From now on, please sign in with the new address below:
-
- If you weren't expecting this change, contact your administrator immediately.
- Your old address (the one this message was sent to) can no longer be used to
- sign in.
-
-
- Thanks,
- ${escapeHtml(portName)}
-
`;
-
- const text = [
- `Your sign-in email was changed`,
- '',
- `${data.changedByDisplayName ?? 'An administrator'} updated the email linked to your ${portName} account.`,
- `From now on, sign in with: ${data.newEmail}`,
- '',
- data.loginUrl ? `Sign in: ${data.loginUrl}` : '',
- '',
- `If you weren't expecting this change, contact your administrator immediately.`,
- ]
- .filter(Boolean)
- .join('\n');
-
- const html = renderShell({
- branding: overrides?.branding ?? null,
- title: subject,
- body,
- });
-
- return { subject, html, text };
-}
-
-function escapeHtml(s: string): string {
- return s
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
-}
diff --git a/src/lib/email/templates/admin-email-change.tsx b/src/lib/email/templates/admin-email-change.tsx
new file mode 100644
index 00000000..dc02620c
--- /dev/null
+++ b/src/lib/email/templates/admin-email-change.tsx
@@ -0,0 +1,109 @@
+import { Button, Hr, Text, render } from '@react-email/components';
+import * as React from 'react';
+
+import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
+
+interface AdminEmailChangeData {
+ recipientName?: string;
+ newEmail: string;
+ changedByDisplayName?: string;
+ loginUrl?: string;
+ portName?: string;
+}
+
+interface RenderOpts {
+ branding?: BrandingShell | null;
+}
+
+function AdminEmailChangeBody({
+ portName,
+ newEmail,
+ recipientName,
+ changedByDisplayName,
+ loginUrl,
+ accent,
+}: AdminEmailChangeData & { portName: string; accent: string }) {
+ const greeting = recipientName ? `Hello ${recipientName},` : 'Hello,';
+ const adminLine = changedByDisplayName
+ ? `${changedByDisplayName} (an administrator)`
+ : 'an administrator';
+ return (
+ <>
+
+ Your sign-in email was changed
+
+ {greeting}
+
+ {adminLine} just updated the email address linked to your {portName} account. From now on,
+ please sign in with the new address below:
+
+
+ {newEmail}
+
+ {loginUrl ? (
+
+
+
+ ) : null}
+
+
+ If you weren't expecting this change, contact your administrator immediately. Your old
+ address (the one this message was sent to) can no longer be used to sign in.
+
+
+ Thanks,
+
+ {portName}
+
+ >
+ );
+}
+
+export async function adminEmailChangeEmail(
+ data: AdminEmailChangeData,
+ overrides?: RenderOpts,
+): Promise<{ subject: string; html: string; text: string }> {
+ const portName = data.portName ?? 'Port Nimara';
+ const subject = `An administrator updated your ${portName} sign-in email`;
+ const accent = brandingPrimaryColor(overrides?.branding);
+
+ const body = await render(
+ ,
+ {
+ pretty: false,
+ },
+ );
+
+ const text = [
+ `Your sign-in email was changed`,
+ '',
+ `${data.changedByDisplayName ?? 'An administrator'} updated the email linked to your ${portName} account.`,
+ `From now on, sign in with: ${data.newEmail}`,
+ '',
+ data.loginUrl ? `Sign in: ${data.loginUrl}` : '',
+ '',
+ `If you weren't expecting this change, contact your administrator immediately.`,
+ ]
+ .filter(Boolean)
+ .join('\n');
+
+ return {
+ subject,
+ html: renderShell({ branding: overrides?.branding ?? null, title: subject, body }),
+ text,
+ };
+}
diff --git a/src/lib/email/templates/crm-invite.ts b/src/lib/email/templates/crm-invite.ts
deleted file mode 100644
index 3dc5337c..00000000
--- a/src/lib/email/templates/crm-invite.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
-
-interface InviteData {
- link: string;
- ttlHours: number;
- recipientName?: string;
- isSuperAdmin: boolean;
- /** Display name for the port — falls back to "Port Nimara" so the
- * pre-multi-tenant default still reads correctly. */
- portName?: string;
-}
-
-interface RenderOpts {
- branding?: BrandingShell | null;
-}
-
-export function crmInviteEmail(
- data: InviteData,
- overrides?: RenderOpts,
-): {
- subject: string;
- html: string;
- text: string;
-} {
- const portName = data.portName ?? 'Port Nimara';
- const subject = `You're invited to the ${portName} CRM`;
- const greeting = data.recipientName ? `Dear ${escapeHtml(data.recipientName)},` : 'Welcome,';
- const role = data.isSuperAdmin ? 'super administrator' : 'administrator';
- const accent = brandingPrimaryColor(overrides?.branding);
-
- const body = `
-
- Welcome to the ${escapeHtml(portName)} CRM
-
-
${greeting}
-
- You've been invited to the ${escapeHtml(portName)} CRM as a ${role}. Click the
- button below to set your password and activate your account. The
- link expires in ${data.ttlHours} hours.
-
- If the button doesn't work, paste this link into your browser:
- ${data.link}
-
-
- Thank you,
- ${escapeHtml(portName)} CRM
-
`;
-
- const text = [
- `Welcome to the ${portName} CRM`,
- '',
- `You've been invited as a ${role}.`,
- `Set up your account: ${data.link}`,
- '',
- `The link expires in ${data.ttlHours} hours.`,
- '',
- `Thank you,`,
- `${portName} CRM`,
- ].join('\n');
-
- return {
- subject,
- html: renderShell({ title: subject, body, branding: overrides?.branding }),
- text,
- };
-}
-
-function escapeHtml(str: string): string {
- return str
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
-}
diff --git a/src/lib/email/templates/crm-invite.tsx b/src/lib/email/templates/crm-invite.tsx
new file mode 100644
index 00000000..459bb69f
--- /dev/null
+++ b/src/lib/email/templates/crm-invite.tsx
@@ -0,0 +1,121 @@
+import { Button, Hr, Link, Text, render } from '@react-email/components';
+import * as React from 'react';
+
+import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
+
+interface InviteData {
+ link: string;
+ ttlHours: number;
+ recipientName?: string;
+ isSuperAdmin: boolean;
+ /** Display name for the port — falls back to "Port Nimara" so the
+ * pre-multi-tenant default still reads correctly. */
+ portName?: string;
+}
+
+interface RenderOpts {
+ branding?: BrandingShell | null;
+}
+
+function InviteBody({
+ portName,
+ link,
+ ttlHours,
+ recipientName,
+ role,
+ accent,
+}: {
+ portName: string;
+ link: string;
+ ttlHours: number;
+ recipientName?: string;
+ role: string;
+ accent: string;
+}) {
+ const greeting = recipientName ? `Dear ${recipientName},` : 'Welcome,';
+ return (
+ <>
+
+ Welcome to the {portName} CRM
+
+ {greeting}
+
+ You've been invited to the {portName} CRM as a {role}. Click the button below to set
+ your password and activate your account. The link expires in {ttlHours} hours.
+
+
+
+
+
+
+ If the button doesn't work, paste this link into your browser:
+
+
+ {link}
+
+
+
+ Thank you,
+
+ {portName} CRM
+
+ >
+ );
+}
+
+export async function crmInviteEmail(
+ data: InviteData,
+ overrides?: RenderOpts,
+): Promise<{ subject: string; html: string; text: string }> {
+ const portName = data.portName ?? 'Port Nimara';
+ const subject = `You're invited to the ${portName} CRM`;
+ const role = data.isSuperAdmin ? 'super administrator' : 'administrator';
+ const accent = brandingPrimaryColor(overrides?.branding);
+
+ const body = await render(
+ ,
+ { pretty: false },
+ );
+
+ const text = [
+ `Welcome to the ${portName} CRM`,
+ '',
+ `You've been invited as a ${role}.`,
+ `Set up your account: ${data.link}`,
+ '',
+ `The link expires in ${data.ttlHours} hours.`,
+ '',
+ `Thank you,`,
+ `${portName} CRM`,
+ ].join('\n');
+
+ return {
+ subject,
+ html: renderShell({ title: subject, body, branding: overrides?.branding }),
+ text,
+ };
+}
diff --git a/src/lib/email/templates/document-signing.ts b/src/lib/email/templates/document-signing.ts
deleted file mode 100644
index 6479e50f..00000000
--- a/src/lib/email/templates/document-signing.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-/**
- * Branded transactional emails for the Documenso signing lifecycle.
- *
- * Three template families:
- *
- * 1. `signingInvitation` — sent to a single signer when their turn
- * to sign comes up. Used both for the initial client invite (after
- * EOI/contract/reservation generation) AND for the cascading
- * "your turn" emails when an earlier signer completes (developer
- * after client signs, approver after developer signs, etc).
- *
- * 2. `signingCompleted` — sent to ALL signers (with the finalized
- * signed PDF as an attachment) when the document reaches a fully
- * signed state. Mirrors the old system's
- * `sendFinalizedDocumentToSignatories` flow.
- *
- * 3. `signingReminder` — sent when a rep nudges an unsigned recipient
- * manually OR when the rate-limited reminder service fires. Same
- * visual shape as `signingInvitation` with different copy.
- *
- * All three use the per-port `BrandingShell` (logo + primary color +
- * header/footer HTML) so each tenant's outbound emails match its
- * brand. The signing URL passed in is expected to already be
- * embedded-format (e.g. `https://portnimara.com/sign//`)
- * — the caller (interest service / webhook handler) does the
- * transformation from the raw Documenso URL.
- */
-
-import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
-
-interface RenderOpts {
- subject?: string | null;
- branding?: BrandingShell | null;
-}
-
-interface InvitationData {
- /** Display name for the recipient — used in the greeting. */
- recipientName: string;
- /** Friendly document type label. e.g. "Expression of Interest", "Sales Contract", "Reservation Agreement". */
- documentLabel: string;
- /** Optional. The signer's role: 'client' | 'developer' | 'approver' | 'witness' etc. Drives copy nuance. */
- signerRole?: string | null;
- /** Embedded signing URL (already wrapped to the public branded host). */
- signingUrl: string;
- /** Port name to brand the email. */
- portName: string;
- /** Sales rep / sender name shown in the closing. Falls back to "{portName} team". */
- senderName?: string | null;
- /** Optional plain-text message from the rep to include above the CTA. */
- customMessage?: string | null;
-}
-
-export function signingInvitationEmail(
- data: InvitationData,
- overrides?: RenderOpts,
-): { subject: string; html: string; text: string } {
- const accent = brandingPrimaryColor(overrides?.branding);
- const docLabelEsc = escapeHtml(data.documentLabel);
- const subject = overrides?.subject
- ? overrides.subject
- .replace(/\{\{documentLabel\}\}/g, data.documentLabel)
- .replace(/\{\{portName\}\}/g, data.portName)
- .replace(/\{\{recipientName\}\}/g, data.recipientName)
- : `${data.documentLabel} ready to sign — ${data.portName}`;
-
- const greeting = `Dear ${escapeHtml(data.recipientName)},`;
- const closer = data.senderName
- ? `${escapeHtml(data.senderName)} ${escapeHtml(data.portName)}`
- : `The ${escapeHtml(data.portName)} team`;
-
- // Slightly different lead paragraph based on signer role so the
- // developer / approver emails don't read as if they're the client.
- const isClient = (data.signerRole ?? 'client') === 'client';
- const leadCopy = isClient
- ? `Your ${docLabelEsc} for ${escapeHtml(data.portName)} is ready for signing. Click the button below to review and sign — it should only take a couple of minutes.`
- : data.signerRole === 'approver'
- ? `An ${docLabelEsc} is awaiting your approval. The earlier signers have completed their parts; please review and sign to finalise the document.`
- : `An ${docLabelEsc} is awaiting your signature. The client has already signed; you're the next signer in the chain.`;
-
- const customMessageBlock = data.customMessage
- ? `
- We sent you a ${docLabelEsc} ${escapeHtml(data.invitedAgo)} that's still awaiting your signature. If you've already signed, please disregard this message — it can take a few minutes for our system to catch up.
-
- Thank you,
- The ${escapeHtml(data.portName)} team
-
`;
-
- const text = `${greeting}\n\nWe sent you a ${data.documentLabel} ${data.invitedAgo} that's still awaiting your signature. ${data.customMessage ? '\n\n' + data.customMessage + '\n\n' : ''}\n\nSign here: ${data.signingUrl}\n\nThank you,\nThe ${data.portName} team`;
-
- return {
- subject,
- html: renderShell({ title: subject, body, branding: overrides?.branding }),
- text,
- };
-}
-
-// ─── Helpers ────────────────────────────────────────────────────────────────
-
-function escapeHtml(input: string): string {
- return input
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
-}
-
-function stripTags(html: string): string {
- return html.replace(/<[^>]+>/g, '');
-}
diff --git a/src/lib/email/templates/document-signing.tsx b/src/lib/email/templates/document-signing.tsx
new file mode 100644
index 00000000..4f978ea5
--- /dev/null
+++ b/src/lib/email/templates/document-signing.tsx
@@ -0,0 +1,327 @@
+/**
+ * Branded transactional emails for the Documenso signing lifecycle.
+ *
+ * Three template families:
+ *
+ * 1. signingInvitation — sent to a single signer when their turn to sign
+ * comes up. Used both for initial client invites AND cascading "your
+ * turn" emails when an earlier signer completes.
+ *
+ * 2. signingCompleted — sent to ALL signers (with the finalized signed
+ * PDF as an attachment) when the document reaches a fully signed state.
+ *
+ * 3. signingReminder — sent when a rep nudges manually OR when the
+ * rate-limited reminder service fires.
+ *
+ * All three use the per-port BrandingShell. The signing URL is expected
+ * to already be embedded-format (e.g. /sign//) — the caller
+ * does the transformation from the raw Documenso URL.
+ */
+
+import { Button, Hr, Link, Text, render } from '@react-email/components';
+import * as React from 'react';
+
+import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
+
+interface RenderOpts {
+ subject?: string | null;
+ branding?: BrandingShell | null;
+}
+
+// ─── 1. Invitation ───────────────────────────────────────────────────────────
+
+interface InvitationData {
+ recipientName: string;
+ documentLabel: string;
+ signerRole?: string | null;
+ signingUrl: string;
+ portName: string;
+ senderName?: string | null;
+ customMessage?: string | null;
+}
+
+function InvitationBody({ data, accent }: { data: InvitationData; accent: string }) {
+ const greeting = `Dear ${data.recipientName},`;
+ const isClient = (data.signerRole ?? 'client') === 'client';
+ const leadCopy = isClient
+ ? `Your ${data.documentLabel} for ${data.portName} is ready for signing. Click the button below to review and sign — it should only take a couple of minutes.`
+ : data.signerRole === 'approver'
+ ? `An ${data.documentLabel} is awaiting your approval. The earlier signers have completed their parts; please review and sign to finalise the document.`
+ : `An ${data.documentLabel} is awaiting your signature. The client has already signed; you're the next signer in the chain.`;
+
+ return (
+ <>
+
+ {data.documentLabel} ready to sign
+
+ {greeting}
+ {leadCopy}
+ {data.customMessage ? (
+
+ {data.customMessage}
+
+ ) : null}
+
+
+
+
+
+ If the button doesn't work, paste this link into your browser:
+
+
+ {data.signingUrl}
+
+
+
+ Signing happens directly inside our website — your data isn't sent to a third-party
+ signing service.
+
+
+ Thank you,
+
+ {data.senderName ? (
+ <>
+ {data.senderName}
+
+ {data.portName}
+ >
+ ) : (
+ The {data.portName} team
+ )}
+
+ >
+ );
+}
+
+export async function signingInvitationEmail(
+ data: InvitationData,
+ overrides?: RenderOpts,
+): Promise<{ subject: string; html: string; text: string }> {
+ const accent = brandingPrimaryColor(overrides?.branding);
+ const subject = overrides?.subject
+ ? overrides.subject
+ .replace(/\{\{documentLabel\}\}/g, data.documentLabel)
+ .replace(/\{\{portName\}\}/g, data.portName)
+ .replace(/\{\{recipientName\}\}/g, data.recipientName)
+ : `${data.documentLabel} ready to sign — ${data.portName}`;
+
+ const body = await render(, { pretty: false });
+
+ const isClient = (data.signerRole ?? 'client') === 'client';
+ const leadText = isClient
+ ? `Your ${data.documentLabel} for ${data.portName} is ready for signing.`
+ : data.signerRole === 'approver'
+ ? `An ${data.documentLabel} is awaiting your approval.`
+ : `An ${data.documentLabel} is awaiting your signature.`;
+ const text = `Dear ${data.recipientName},\n\n${leadText}\n\n${data.customMessage ? data.customMessage + '\n\n' : ''}Sign here: ${data.signingUrl}\n\nThank you,\n${data.senderName ?? `The ${data.portName} team`}`;
+
+ return {
+ subject,
+ html: renderShell({ title: subject, body, branding: overrides?.branding }),
+ text,
+ };
+}
+
+// ─── 2. Completed ─────────────────────────────────────────────────────────────
+
+interface CompletedData {
+ recipientName: string;
+ documentLabel: string;
+ clientName: string;
+ portName: string;
+ completedAt: Date;
+}
+
+function CompletedBody({
+ data,
+ accent,
+ completedDateStr,
+}: {
+ data: CompletedData;
+ accent: string;
+ completedDateStr: string;
+}) {
+ const greeting = `Dear ${data.recipientName},`;
+ return (
+ <>
+
+ {data.documentLabel} signed by all parties
+
+ {greeting}
+
+ The {data.documentLabel} for {data.clientName} has been signed by every
+ party as of {completedDateStr}.
+
+
+ The fully signed PDF is attached to this email for your records. A copy has also been stored
+ in the {data.portName} CRM.
+
+
+ Thank you,
+
+ The {data.portName} team
+
+ >
+ );
+}
+
+export async function signingCompletedEmail(
+ data: CompletedData,
+ overrides?: RenderOpts,
+): Promise<{ subject: string; html: string; text: string }> {
+ const accent = brandingPrimaryColor(overrides?.branding);
+ const subject = overrides?.subject
+ ? overrides.subject
+ .replace(/\{\{documentLabel\}\}/g, data.documentLabel)
+ .replace(/\{\{clientName\}\}/g, data.clientName)
+ .replace(/\{\{portName\}\}/g, data.portName)
+ : `${data.documentLabel} fully signed — ${data.clientName}`;
+ const completedDateStr = data.completedAt.toLocaleString('en-GB', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+
+ const body = await render(
+ ,
+ { pretty: false },
+ );
+
+ const text = `Dear ${data.recipientName},\n\nThe ${data.documentLabel} for ${data.clientName} has been signed by all parties on ${completedDateStr}. The signed PDF is attached for your records.\n\nThank you,\nThe ${data.portName} team`;
+
+ return {
+ subject,
+ html: renderShell({ title: subject, body, branding: overrides?.branding }),
+ text,
+ };
+}
+
+// ─── 3. Reminder ──────────────────────────────────────────────────────────────
+
+interface ReminderData {
+ recipientName: string;
+ documentLabel: string;
+ signingUrl: string;
+ portName: string;
+ invitedAgo: string;
+ customMessage?: string | null;
+}
+
+function ReminderBody({ data, accent }: { data: ReminderData; accent: string }) {
+ const greeting = `Dear ${data.recipientName},`;
+ return (
+ <>
+
+ Just a quick reminder
+
+ {greeting}
+
+ We sent you a {data.documentLabel} {data.invitedAgo} that's still awaiting your
+ signature. If you've already signed, please disregard this message — it can take a few
+ minutes for our system to catch up.
+
+ {data.customMessage ? (
+
+ {data.customMessage}
+
+ ) : null}
+
+
+
+
+
+ Direct link:{' '}
+
+ {data.signingUrl}
+
+
+
+ Thank you,
+
+ The {data.portName} team
+
+ >
+ );
+}
+
+export async function signingReminderEmail(
+ data: ReminderData,
+ overrides?: RenderOpts,
+): Promise<{ subject: string; html: string; text: string }> {
+ const accent = brandingPrimaryColor(overrides?.branding);
+ const subject = overrides?.subject
+ ? overrides.subject
+ .replace(/\{\{documentLabel\}\}/g, data.documentLabel)
+ .replace(/\{\{portName\}\}/g, data.portName)
+ : `Friendly reminder: ${data.documentLabel} still awaiting your signature — ${data.portName}`;
+
+ const body = await render(, { pretty: false });
+
+ const text = `Dear ${data.recipientName},\n\nWe sent you a ${data.documentLabel} ${data.invitedAgo} that's still awaiting your signature. ${data.customMessage ? '\n\n' + data.customMessage + '\n\n' : ''}\n\nSign here: ${data.signingUrl}\n\nThank you,\nThe ${data.portName} team`;
+
+ return {
+ subject,
+ html: renderShell({ title: subject, body, branding: overrides?.branding }),
+ text,
+ };
+}
diff --git a/src/lib/email/templates/inquiry-client-confirmation.ts b/src/lib/email/templates/inquiry-client-confirmation.ts
deleted file mode 100644
index 18218808..00000000
--- a/src/lib/email/templates/inquiry-client-confirmation.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { brandingPrimaryColor, renderShell, type BrandingShell } from '@/lib/email/shell';
-
-export interface InquiryClientConfirmationData {
- firstName: string;
- mooringNumber: string | null;
- contactEmail: string;
- /** Display name; falls back to "Port Nimara". */
- portName?: string;
-}
-
-interface RenderOpts {
- branding?: BrandingShell | null;
-}
-
-export function inquiryClientConfirmation(
- data: InquiryClientConfirmationData,
- overrides?: RenderOpts,
-) {
- const { firstName, mooringNumber, contactEmail } = data;
- const portName = data.portName ?? 'Port Nimara';
-
- const berthText = mooringNumber ? `Berth ${mooringNumber}` : `a ${portName} Berth`;
- const subject = mooringNumber
- ? `Thank You for Your Interest in Berth ${mooringNumber}`
- : `Thank You for Your Interest in a ${portName} Berth`;
-
- const accent = brandingPrimaryColor(overrides?.branding);
-
- const body = `
-
Dear ${escapeHtml(firstName)},
-
- Thank you for expressing interest in ${escapeHtml(berthText)}.
- Our team has registered your interest, and we will reach out to you very shortly
- by your preferred method of contact with more information.
-
- Thank you for expressing interest in ${escapeHtml(portName)} residences. Our residential
- sales team has received your inquiry and will reach out to you shortly with
- more information.
-