@@ -10,33 +10,24 @@ function client() {
1010}
1111
1212// Shared CrawlProof email shell — mirrors supabase/templates/* so transactional
13- // mail (audit-ready, receipts) looks the same as auth mail. Score colors hint
14- // at how the user should feel about the result.
13+ // mail (audit-ready, receipts) looks the same as auth mail.
1514function scoreAccent ( score : number ) : { bg : string ; fg : string } {
1615 if ( score >= 80 ) return { bg : "#6ee7b7" , fg : "#042f1a" } ;
1716 if ( score >= 60 ) return { bg : "#fcd34d" , fg : "#3a2a05" } ;
1817 return { bg : "#fca5a5" , fg : "#3a0808" } ;
1918}
2019
21- export function auditReadyEmailHtml ( input : {
22- targetUrl : string ;
23- score : number ;
24- reportUrl : string ;
25- pdfAttached ?: boolean ;
20+ function emailShell ( input : {
21+ title : string ;
22+ innerHtml : string ;
23+ footerNote ?: string ;
2624} ) : string {
27- const host = ( ( ) => {
28- try { return new URL ( input . targetUrl ) . hostname ; } catch { return input . targetUrl ; }
29- } ) ( ) ;
30- const accent = scoreAccent ( input . score ) ;
31- const attachedLine = input . pdfAttached
32- ? `<p style="margin:14px 0 0;font-size:13px;line-height:1.6;color:#64748b;">A PDF copy of this report is attached to this email.</p>`
33- : "" ;
3425 return `<!doctype html>
3526<html lang="en">
3627<head>
3728<meta charset="utf-8">
3829<meta name="viewport" content="width=device-width,initial-scale=1">
39- <title>Your CrawlProof audit for ${ host } is ready </title>
30+ <title>${ input . title } </title>
4031</head>
4132<body style="margin:0;padding:0;background:#0b0d10;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#e7e9ee;">
4233 <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#0b0d10;padding:32px 16px;">
@@ -55,7 +46,38 @@ export function auditReadyEmailHtml(input: {
5546 </table>
5647 </td>
5748 </tr>
58- <tr>
49+ ${ input . innerHtml }
50+ ${ input . footerNote ? `<tr>
51+ <td style="padding:16px 32px 28px;border-top:1px solid #1f2630;">
52+ <p style="margin:0;font-size:12px;line-height:1.6;color:#64748b;">${ input . footerNote } </p>
53+ </td>
54+ </tr>` : "" }
55+ </table>
56+ <p style="margin:18px 0 0;font-size:11px;color:#475569;">
57+ CrawlProof · See your site the way AI crawlers do.<br>
58+ <a href="${ env . siteUrl } " style="color:#64748b;">${ env . siteUrl } </a>
59+ </p>
60+ </td>
61+ </tr>
62+ </table>
63+ </body>
64+ </html>` ;
65+ }
66+
67+ export function auditReadyEmailHtml ( input : {
68+ targetUrl : string ;
69+ score : number ;
70+ reportUrl : string ;
71+ pdfAttached ?: boolean ;
72+ } ) : string {
73+ const host = ( ( ) => {
74+ try { return new URL ( input . targetUrl ) . hostname ; } catch { return input . targetUrl ; }
75+ } ) ( ) ;
76+ const accent = scoreAccent ( input . score ) ;
77+ const attachedLine = input . pdfAttached
78+ ? `<p style="margin:14px 0 0;font-size:13px;line-height:1.6;color:#64748b;">A PDF copy of this report is attached to this email.</p>`
79+ : "" ;
80+ const innerHtml = `<tr>
5981 <td style="padding:24px 32px 0;">
6082 <h1 style="margin:0;font-size:22px;line-height:1.3;font-weight:800;color:#e7e9ee;">
6183 Your audit for ${ host } is ready
@@ -93,25 +115,82 @@ export function auditReadyEmailHtml(input: {
93115 </p>
94116 ${ attachedLine }
95117 </td>
118+ </tr>` ;
119+ return emailShell ( {
120+ title : `Your CrawlProof audit for ${ host } is ready` ,
121+ innerHtml,
122+ footerNote : "Want to track this site over time? Sign in and add it as a project to schedule weekly or monthly re-audits." ,
123+ } ) ;
124+ }
125+
126+ export function purchaseReceiptEmailHtml ( input : {
127+ packLabel : string ;
128+ creditsAdded : number ;
129+ amountFormatted : string ;
130+ currency : string ;
131+ paymentId : string ;
132+ txHash ?: string | null ;
133+ } ) : string {
134+ const txRow = input . txHash
135+ ? `<tr>
136+ <td style="padding:6px 0;color:#64748b;font-size:11px;text-transform:uppercase;letter-spacing:.1em;">Tx</td>
137+ <td style="padding:6px 0;color:#e7e9ee;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;word-break:break-all;">${ input . txHash } </td>
138+ </tr>`
139+ : "" ;
140+ const innerHtml = `<tr>
141+ <td style="padding:24px 32px 0;">
142+ <h1 style="margin:0;font-size:22px;line-height:1.3;font-weight:800;color:#e7e9ee;">
143+ Thanks for your purchase
144+ </h1>
145+ <p style="margin:12px 0 0;font-size:14px;line-height:1.6;color:#9aa3b2;">
146+ <strong style="color:#6ee7b7;">${ input . creditsAdded } credit${ input . creditsAdded === 1 ? "" : "s" } </strong>
147+ added to your CrawlProof account.
148+ </p>
149+ </td>
96150 </tr>
97151 <tr>
98- <td style="padding:16px 32px 28px;border-top:1px solid #1f2630;">
99- <p style="margin:0;font-size:12px;line-height:1.6;color:#64748b;">
100- Want to track this site over time? Sign in and add it as a
101- project to schedule weekly or monthly re-audits.
102- </p>
152+ <td style="padding:20px 32px 0;">
153+ <table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background:#0b0d10;border:1px solid #1f2630;border-radius:10px;padding:14px 18px;">
154+ <tr>
155+ <td style="padding:6px 0;color:#64748b;font-size:11px;text-transform:uppercase;letter-spacing:.1em;">Pack</td>
156+ <td style="padding:6px 0;color:#e7e9ee;font-size:14px;font-weight:600;text-align:right;">${ input . packLabel } </td>
157+ </tr>
158+ <tr>
159+ <td style="padding:6px 0;color:#64748b;font-size:11px;text-transform:uppercase;letter-spacing:.1em;">Credits</td>
160+ <td style="padding:6px 0;color:#e7e9ee;font-size:14px;font-weight:600;text-align:right;">${ input . creditsAdded } </td>
161+ </tr>
162+ <tr>
163+ <td style="padding:6px 0;color:#64748b;font-size:11px;text-transform:uppercase;letter-spacing:.1em;">Total</td>
164+ <td style="padding:6px 0;color:#e7e9ee;font-size:14px;font-weight:600;text-align:right;">${ input . amountFormatted } ${ input . currency } </td>
165+ </tr>
166+ <tr>
167+ <td style="padding:6px 0;color:#64748b;font-size:11px;text-transform:uppercase;letter-spacing:.1em;">Order</td>
168+ <td style="padding:6px 0;color:#e7e9ee;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:12px;text-align:right;word-break:break-all;">${ input . paymentId } </td>
169+ </tr>
170+ ${ txRow }
171+ </table>
103172 </td>
104173 </tr>
105- </table>
106- <p style="margin:18px 0 0;font-size:11px;color:#475569;">
107- CrawlProof · See your site the way AI crawlers do.<br>
108- <a href="${ env . siteUrl } " style="color:#64748b;">${ env . siteUrl } </a>
109- </p>
110- </td>
111- </tr>
112- </table>
113- </body>
114- </html>` ;
174+ <tr>
175+ <td style="padding:22px 32px 8px;">
176+ <a href="${ env . siteUrl } /settings/billing"
177+ style="display:inline-block;padding:12px 22px;background:#6ee7b7;color:#042f1a;text-decoration:none;border-radius:10px;font-weight:700;font-size:15px;">
178+ View billing →
179+ </a>
180+ </td>
181+ </tr>
182+ <tr>
183+ <td style="padding:8px 32px 24px;">
184+ <p style="margin:8px 0 0;font-size:13px;line-height:1.6;color:#64748b;">
185+ A PDF copy of this receipt is attached for your records.
186+ </p>
187+ </td>
188+ </tr>` ;
189+ return emailShell ( {
190+ title : `Your CrawlProof receipt` ,
191+ innerHtml,
192+ footerNote : "Credits never expire and can be used on any scan engine." ,
193+ } ) ;
115194}
116195
117196export async function sendAuditReadyEmail ( input : {
@@ -201,20 +280,19 @@ export async function sendPurchaseReceiptEmail(input: {
201280 minimumFractionDigits : input . amountCents % 100 === 0 ? 0 : 2 ,
202281 } ) } `;
203282 const filename = `crawlproof-receipt-${ input . paymentId . slice ( 0 , 12 ) } .pdf` ;
204- const txLine = input . txHash
205- ? `<p style="color:#666;font-size:12px"><strong>Tx:</strong> <code>${ input . txHash } </code></p>`
206- : "" ;
207283
208284 const res = await c . emails . send ( {
209285 from : env . resendFrom ,
210286 to : input . to ,
211287 subject : `Your CrawlProof receipt — ${ input . creditsAdded } credit${ input . creditsAdded === 1 ? "" : "s" } ` ,
212- html : `<p>Thanks for your purchase!</p>
213- <p><strong>${ input . packLabel } </strong> — ${ input . creditsAdded } credit${ input . creditsAdded === 1 ? "" : "s" } added to your account.</p>
214- <p><strong>Total paid:</strong> ${ amount } ${ input . currency } </p>
215- <p><strong>Order:</strong> ${ input . paymentId } </p>
216- ${ txLine }
217- <p>A PDF copy of this receipt is attached.</p>` ,
288+ html : purchaseReceiptEmailHtml ( {
289+ packLabel : input . packLabel ,
290+ creditsAdded : input . creditsAdded ,
291+ amountFormatted : amount ,
292+ currency : input . currency ,
293+ paymentId : input . paymentId ,
294+ txHash : input . txHash ,
295+ } ) ,
218296 attachments : [ { filename, content : input . pdf . toString ( "base64" ) } ] ,
219297 } ) ;
220298 if ( res . error ) return { sent : false , error : String ( res . error ) } ;
0 commit comments