Files
pn-new-crm/src/lib/email/templates/inquiry-client-confirmation.tsx
Matt b2588ecdd8 fix(audit-wave-1): route all email-template URLs through safeUrl
Closes Wave 1.4 (CRITICAL). Three templates still inlined URLs
directly into `href` without the existing safeUrl() helper:

- inquiry-client-confirmation: `mailto:${contactEmail}` href —
  user-supplied email straight to an HTML attribute.
- inquiry-sales-notification: `${crmUrl}` from inquiry form input.
- residential-inquiry: same `mailto:${contactEmail}` pattern.

Each call now passes through `safeUrl()` from `@/lib/email/shell`,
which (a) scheme-allow-lists to http(s)/mailto/tel/root-relative and
(b) HTML-attribute-escapes the result. A stray `"` in any URL would
have escaped the attribute; a `javascript:` scheme would have
triggered XSS in webmail clients that run scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:08:51 +02:00

97 lines
2.8 KiB
TypeScript

import { Link, Text, render } from '@react-email/components';
import * as React from 'react';
import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell';
export interface InquiryClientConfirmationData {
firstName: string;
mooringNumber: string | null;
contactEmail: string;
portName?: string;
}
interface RenderOpts {
branding?: BrandingShell | null;
}
function ClientConfirmationBody({
firstName,
berthText,
contactEmail,
portName,
accent,
}: {
firstName: string;
berthText: string;
contactEmail: string;
portName: string;
accent: string;
}) {
return (
<>
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>Dear {firstName},</Text>
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>
Thank you for expressing interest in {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.
</Text>
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>
If you have any questions, please feel free to reach out to us at{' '}
<Link
href={safeUrl(`mailto:${contactEmail}`)}
style={{ color: accent, textDecoration: 'underline' }}
>
{contactEmail}
</Link>
.
</Text>
<Text style={{ fontSize: '16px' }}>
Best regards,
<br />
The {portName} Sales Team
</Text>
</>
);
}
export async 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 = await render(
<ClientConfirmationBody
firstName={firstName}
berthText={berthText}
contactEmail={contactEmail}
portName={portName}
accent={accent}
/>,
{ pretty: false },
);
const text = [
`Dear ${firstName},`,
'',
`Thank you for expressing interest in ${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.`,
'',
`If you have any questions, please feel free to reach out to us at ${contactEmail}.`,
'',
'Best regards,',
`The ${portName} Sales Team`,
].join('\n');
return {
subject,
html: renderShell({ title: subject, body, branding: overrides?.branding }),
text,
};
}