@ucptools/validator 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.js +96 -0
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/mock-server.d.ts +20 -0
  20. package/dist/cli/mock-server.d.ts.map +1 -0
  21. package/dist/cli/mock-server.js +261 -0
  22. package/dist/cli/mock-server.js.map +1 -0
  23. package/dist/db/index.d.ts +8 -2
  24. package/dist/db/index.d.ts.map +1 -1
  25. package/dist/db/index.js +22 -5
  26. package/dist/db/index.js.map +1 -1
  27. package/dist/db/schema.d.ts +3570 -128
  28. package/dist/db/schema.d.ts.map +1 -1
  29. package/dist/db/schema.js +377 -17
  30. package/dist/db/schema.js.map +1 -1
  31. package/dist/db/utils.d.ts +252 -0
  32. package/dist/db/utils.d.ts.map +1 -0
  33. package/dist/db/utils.js +295 -0
  34. package/dist/db/utils.js.map +1 -0
  35. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -1
  36. package/dist/feed-analyzer/feed-analyzer.js +218 -4
  37. package/dist/feed-analyzer/feed-analyzer.js.map +1 -1
  38. package/dist/feed-analyzer/types.d.ts +82 -1
  39. package/dist/feed-analyzer/types.d.ts.map +1 -1
  40. package/dist/feed-analyzer/types.js +13 -0
  41. package/dist/feed-analyzer/types.js.map +1 -1
  42. package/dist/generator/profile-builder.d.ts.map +1 -1
  43. package/dist/generator/profile-builder.js +158 -115
  44. package/dist/generator/profile-builder.js.map +1 -1
  45. package/dist/lib/analytics.d.ts +349 -0
  46. package/dist/lib/analytics.d.ts.map +1 -0
  47. package/dist/lib/analytics.js +198 -0
  48. package/dist/lib/analytics.js.map +1 -0
  49. package/dist/security/security-scanner.d.ts.map +1 -1
  50. package/dist/security/security-scanner.js +130 -2
  51. package/dist/security/security-scanner.js.map +1 -1
  52. package/dist/security/types.d.ts +32 -0
  53. package/dist/security/types.d.ts.map +1 -1
  54. package/dist/security/types.js.map +1 -1
  55. package/dist/services/analytics.d.ts +114 -0
  56. package/dist/services/analytics.d.ts.map +1 -0
  57. package/dist/services/analytics.js +862 -0
  58. package/dist/services/analytics.js.map +1 -0
  59. package/dist/services/badge.d.ts +31 -0
  60. package/dist/services/badge.d.ts.map +1 -0
  61. package/dist/services/badge.js +152 -0
  62. package/dist/services/badge.js.map +1 -0
  63. package/dist/services/cron.d.ts +127 -0
  64. package/dist/services/cron.d.ts.map +1 -0
  65. package/dist/services/cron.js +693 -0
  66. package/dist/services/cron.js.map +1 -0
  67. package/dist/services/directory.d.ts +2 -0
  68. package/dist/services/directory.d.ts.map +1 -1
  69. package/dist/services/directory.js +45 -27
  70. package/dist/services/directory.js.map +1 -1
  71. package/dist/services/email.d.ts +127 -0
  72. package/dist/services/email.d.ts.map +1 -0
  73. package/dist/services/email.js +876 -0
  74. package/dist/services/email.js.map +1 -0
  75. package/dist/services/hosted-profiles.d.ts +77 -0
  76. package/dist/services/hosted-profiles.d.ts.map +1 -0
  77. package/dist/services/hosted-profiles.js +433 -0
  78. package/dist/services/hosted-profiles.js.map +1 -0
  79. package/dist/services/latency.d.ts +67 -0
  80. package/dist/services/latency.d.ts.map +1 -0
  81. package/dist/services/latency.js +274 -0
  82. package/dist/services/latency.js.map +1 -0
  83. package/dist/services/manifest-compliance.d.ts +64 -0
  84. package/dist/services/manifest-compliance.d.ts.map +1 -0
  85. package/dist/services/manifest-compliance.js +271 -0
  86. package/dist/services/manifest-compliance.js.map +1 -0
  87. package/dist/services/monitoring-diff.d.ts +31 -0
  88. package/dist/services/monitoring-diff.d.ts.map +1 -0
  89. package/dist/services/monitoring-diff.js +189 -0
  90. package/dist/services/monitoring-diff.js.map +1 -0
  91. package/dist/services/notifications.d.ts +46 -0
  92. package/dist/services/notifications.d.ts.map +1 -0
  93. package/dist/services/notifications.js +88 -0
  94. package/dist/services/notifications.js.map +1 -0
  95. package/dist/services/posthog.d.ts +43 -0
  96. package/dist/services/posthog.d.ts.map +1 -0
  97. package/dist/services/posthog.js +110 -0
  98. package/dist/services/posthog.js.map +1 -0
  99. package/dist/services/stripe.d.ts +93 -0
  100. package/dist/services/stripe.d.ts.map +1 -0
  101. package/dist/services/stripe.js +490 -0
  102. package/dist/services/stripe.js.map +1 -0
  103. package/dist/services/validation-history.d.ts +99 -0
  104. package/dist/services/validation-history.d.ts.map +1 -0
  105. package/dist/services/validation-history.js +344 -0
  106. package/dist/services/validation-history.js.map +1 -0
  107. package/dist/services/validation-logging.d.ts +103 -0
  108. package/dist/services/validation-logging.d.ts.map +1 -0
  109. package/dist/services/validation-logging.js +210 -0
  110. package/dist/services/validation-logging.js.map +1 -0
  111. package/dist/services/validation.d.ts +119 -0
  112. package/dist/services/validation.d.ts.map +1 -0
  113. package/dist/services/validation.js +1185 -0
  114. package/dist/services/validation.js.map +1 -0
  115. package/dist/simulator/agent-simulator.d.ts.map +1 -1
  116. package/dist/simulator/agent-simulator.js +229 -9
  117. package/dist/simulator/agent-simulator.js.map +1 -1
  118. package/dist/simulator/types.d.ts +26 -0
  119. package/dist/simulator/types.d.ts.map +1 -1
  120. package/dist/simulator/types.js.map +1 -1
  121. package/dist/types/acp-validation.d.ts +87 -0
  122. package/dist/types/acp-validation.d.ts.map +1 -0
  123. package/dist/types/acp-validation.js +40 -0
  124. package/dist/types/acp-validation.js.map +1 -0
  125. package/dist/types/analytics.d.ts +182 -0
  126. package/dist/types/analytics.d.ts.map +1 -0
  127. package/dist/types/analytics.js +7 -0
  128. package/dist/types/analytics.js.map +1 -0
  129. package/dist/types/generator.d.ts +4 -0
  130. package/dist/types/generator.d.ts.map +1 -1
  131. package/dist/types/ucp-profile.d.ts +32 -2
  132. package/dist/types/ucp-profile.d.ts.map +1 -1
  133. package/dist/types/ucp-profile.js +31 -1
  134. package/dist/types/ucp-profile.js.map +1 -1
  135. package/dist/types/validation.d.ts +14 -0
  136. package/dist/types/validation.d.ts.map +1 -1
  137. package/dist/types/validation.js +19 -0
  138. package/dist/types/validation.js.map +1 -1
  139. package/dist/validator/acp/index.d.ts +31 -0
  140. package/dist/validator/acp/index.d.ts.map +1 -0
  141. package/dist/validator/acp/index.js +574 -0
  142. package/dist/validator/acp/index.js.map +1 -0
  143. package/dist/validator/network-validator.d.ts.map +1 -1
  144. package/dist/validator/network-validator.js +23 -13
  145. package/dist/validator/network-validator.js.map +1 -1
  146. package/dist/validator/rules-validator.d.ts +8 -0
  147. package/dist/validator/rules-validator.d.ts.map +1 -1
  148. package/dist/validator/rules-validator.js +159 -43
  149. package/dist/validator/rules-validator.js.map +1 -1
  150. package/dist/validator/structural-validator.d.ts.map +1 -1
  151. package/dist/validator/structural-validator.js +283 -53
  152. package/dist/validator/structural-validator.js.map +1 -1
  153. package/dist/validator/utils.d.ts +62 -0
  154. package/dist/validator/utils.d.ts.map +1 -0
  155. package/dist/validator/utils.js +151 -0
  156. package/dist/validator/utils.js.map +1 -0
  157. package/package.json +45 -12
  158. package/.claude/settings.local.json +0 -60
  159. package/.vercel/README.txt +0 -11
  160. package/.vercel/project.json +0 -1
  161. package/publish-output.txt +0 -0
  162. package/tsconfig.json +0 -20
@@ -0,0 +1,876 @@
1
+ "use strict";
2
+ /**
3
+ * Email Service
4
+ *
5
+ * Handles sending transactional emails via MailerSend API.
6
+ * Used for validation alerts, welcome emails, etc.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.shouldSendAlert = shouldSendAlert;
10
+ exports.sendEmail = sendEmail;
11
+ exports.sendScoreChangeAlert = sendScoreChangeAlert;
12
+ exports.sendSeverityAlert = sendSeverityAlert;
13
+ exports.sendWelcomeEmail = sendWelcomeEmail;
14
+ exports.sendPasswordResetEmail = sendPasswordResetEmail;
15
+ exports.sendTrialDay2Email = sendTrialDay2Email;
16
+ exports.sendTrialDay1Email = sendTrialDay1Email;
17
+ exports.sendTrialDay3Email = sendTrialDay3Email;
18
+ exports.sendTrialDay6Email = sendTrialDay6Email;
19
+ exports.sendTrialExpiredEmail = sendTrialExpiredEmail;
20
+ const MAILERSEND_API_URL = 'https://api.mailersend.com/v1/email';
21
+ /**
22
+ * Determine if an alert email should be sent based on score change
23
+ * Threshold: ±10 points
24
+ */
25
+ function shouldSendAlert(oldScore, newScore) {
26
+ if (oldScore === null || newScore === null) {
27
+ return false; // Don't send alert for first validation or missing data
28
+ }
29
+ const change = Math.abs(newScore - oldScore);
30
+ return change >= 10;
31
+ }
32
+ /**
33
+ * Send an email via MailerSend
34
+ */
35
+ async function sendEmail(options) {
36
+ const apiKey = process.env.MAILERSEND_API_KEY;
37
+ if (!apiKey) {
38
+ console.error('[Email] MAILERSEND_API_KEY not configured');
39
+ return { success: false, error: 'Email service not configured' };
40
+ }
41
+ const fromEmail = options.from || {
42
+ email: process.env.MAILERSEND_FROM_EMAIL || 'alerts@ucptools.dev',
43
+ name: process.env.MAILERSEND_FROM_NAME || 'UCP Tools',
44
+ };
45
+ try {
46
+ const response = await fetch(MAILERSEND_API_URL, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Authorization': `Bearer ${apiKey}`,
50
+ 'Content-Type': 'application/json',
51
+ },
52
+ body: JSON.stringify({
53
+ from: fromEmail,
54
+ to: options.to,
55
+ subject: options.subject,
56
+ text: options.text,
57
+ html: options.html,
58
+ }),
59
+ });
60
+ if (response.ok) {
61
+ const messageId = response.headers.get('x-message-id') || undefined;
62
+ console.log(`[Email] Sent successfully to ${options.to.map(r => r.email).join(', ')}`);
63
+ return { success: true, messageId };
64
+ }
65
+ // Handle errors
66
+ const errorData = await response.json().catch(() => ({}));
67
+ const errorMessage = errorData.message || `HTTP ${response.status}`;
68
+ console.error(`[Email] Failed to send: ${errorMessage}`);
69
+ return { success: false, error: errorMessage };
70
+ }
71
+ catch (error) {
72
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
73
+ console.error('[Email] Send error:', errorMessage);
74
+ return { success: false, error: errorMessage };
75
+ }
76
+ }
77
+ function getGradeColor(grade) {
78
+ switch (grade) {
79
+ case 'A': return '#22c55e';
80
+ case 'B': return '#84cc16';
81
+ case 'C': return '#eab308';
82
+ case 'D': return '#f97316';
83
+ case 'F': return '#ef4444';
84
+ default: return '#6b7280';
85
+ }
86
+ }
87
+ /**
88
+ * Branded email header with logo and gradient title bar
89
+ */
90
+ function getEmailHeader(title, gradient = '#2E86AB 0%, #36B5A2 100%') {
91
+ return `<div style="background: linear-gradient(135deg, ${gradient}); padding: 24px 30px 20px; border-radius: 12px 12px 0 0; text-align: center;">
92
+ <img src="https://ucptools.dev/og-image-icon.png" alt="UCP.tools" width="48" height="48" style="margin-bottom: 12px; border-radius: 10px;" />
93
+ <h1 style="color: white; margin: 0; font-size: 24px;">${title}</h1>
94
+ </div>`;
95
+ }
96
+ /**
97
+ * Branded email footer with social links and email preferences
98
+ */
99
+ function getEmailFooter() {
100
+ return `<hr style="border: none; border-top: 1px solid #e9ecef; margin: 30px 0;">
101
+ <div style="text-align: center;">
102
+ <p style="margin: 0 0 12px 0;">
103
+ <a href="https://www.linkedin.com/company/ucptools" style="color: #2E86AB; text-decoration: none; font-size: 13px; margin: 0 6px;">LinkedIn</a>
104
+ <span style="color: #ccc;">&middot;</span>
105
+ <a href="https://x.com/ucptoolsdev" style="color: #2E86AB; text-decoration: none; font-size: 13px; margin: 0 6px;">X / Twitter</a>
106
+ <span style="color: #ccc;">&middot;</span>
107
+ <a href="https://ucptools.dev" style="color: #2E86AB; text-decoration: none; font-size: 13px; margin: 0 6px;">ucptools.dev</a>
108
+ </p>
109
+ <p style="font-size: 12px; color: #999; margin: 0;">
110
+ UCP Tools &mdash; AI Commerce Readiness Platform<br>
111
+ Questions? Reply to this email &mdash; we read everything.
112
+ </p>
113
+ <p style="font-size: 11px; color: #bbb; margin: 12px 0 0 0;">
114
+ <a href="https://ucptools.dev/dashboard/settings" style="color: #bbb;">Email preferences</a>
115
+ </p>
116
+ </div>`;
117
+ }
118
+ /**
119
+ * Send a score change alert email
120
+ */
121
+ async function sendScoreChangeAlert(params) {
122
+ const { to, domain, oldScore, newScore, oldGrade, newGrade, dashboardUrl } = params;
123
+ const scoreChange = newScore - oldScore;
124
+ const direction = scoreChange > 0 ? 'improved' : 'declined';
125
+ const changeText = scoreChange > 0 ? `+${scoreChange}` : `${scoreChange}`;
126
+ const emoji = scoreChange > 0 ? '📈' : '📉';
127
+ const subject = `${emoji} ${domain}: Score ${direction} (${changeText} points)`;
128
+ const html = `
129
+ <!DOCTYPE html>
130
+ <html>
131
+ <head>
132
+ <meta charset="utf-8">
133
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
134
+ <title>UCP Score Alert</title>
135
+ </head>
136
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
137
+ ${getEmailHeader('UCP Score Alert', '#2E86AB 0%, #1a5c7a 100%')}
138
+
139
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
140
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
141
+
142
+ <p>Your UCP readiness score for <strong>${domain}</strong> has ${direction}:</p>
143
+
144
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
145
+ <div style="display: inline-block; margin: 0 20px;">
146
+ <div style="font-size: 14px; color: #666; margin-bottom: 4px;">Previous</div>
147
+ <div style="font-size: 32px; font-weight: bold; color: #666;">${oldScore}</div>
148
+ <div style="font-size: 18px; font-weight: bold; color: ${getGradeColor(oldGrade)};">Grade ${oldGrade}</div>
149
+ </div>
150
+ <div style="display: inline-block; font-size: 24px; color: #999; margin: 0 10px;">→</div>
151
+ <div style="display: inline-block; margin: 0 20px;">
152
+ <div style="font-size: 14px; color: #666; margin-bottom: 4px;">Current</div>
153
+ <div style="font-size: 32px; font-weight: bold; color: ${scoreChange > 0 ? '#22c55e' : '#ef4444'};">${newScore}</div>
154
+ <div style="font-size: 18px; font-weight: bold; color: ${getGradeColor(newGrade)};">Grade ${newGrade}</div>
155
+ </div>
156
+ </div>
157
+
158
+ <p style="text-align: center;">
159
+ <span style="display: inline-block; padding: 8px 16px; border-radius: 20px; font-weight: 600; background: ${scoreChange > 0 ? '#dcfce7' : '#fee2e2'}; color: ${scoreChange > 0 ? '#166534' : '#991b1b'};">
160
+ ${emoji} ${changeText} points
161
+ </span>
162
+ </p>
163
+
164
+ <p style="text-align: center; margin: 30px 0;">
165
+ <a href="${dashboardUrl}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #1a5c7a 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">View Full Report</a>
166
+ </p>
167
+
168
+ ${getEmailFooter()}
169
+ </div>
170
+ </body>
171
+ </html>
172
+ `;
173
+ const text = `
174
+ UCP Score Alert
175
+
176
+ Hi${to.name ? ` ${to.name}` : ''},
177
+
178
+ Your UCP readiness score for ${domain} has ${direction}:
179
+
180
+ Previous: ${oldScore} (Grade ${oldGrade})
181
+ Current: ${newScore} (Grade ${newGrade})
182
+ Change: ${changeText} points
183
+
184
+ View your full report: ${dashboardUrl}
185
+
186
+ ---
187
+ UCP Tools - AI Commerce Readiness Platform
188
+ Questions? Reply to this email - we read everything.
189
+ `;
190
+ return sendEmail({
191
+ to: [to],
192
+ subject,
193
+ html,
194
+ text,
195
+ });
196
+ }
197
+ /**
198
+ * Send a severity-aware alert email with richer context.
199
+ * Uses severity to determine header color, subject line, and content.
200
+ */
201
+ async function sendSeverityAlert(params) {
202
+ const { to, domain, severity, oldScore, newScore, oldGrade, newGrade, summary, changes, dashboardUrl } = params;
203
+ const config = {
204
+ broken: {
205
+ emoji: '🚨',
206
+ label: 'ACTION REQUIRED — UCP Profile Broken',
207
+ gradient: '#dc2626 0%, #991b1b 100%',
208
+ accentBg: '#fee2e2',
209
+ accentText: '#991b1b',
210
+ },
211
+ degraded: {
212
+ emoji: '⚠️',
213
+ label: 'UCP Profile Degraded',
214
+ gradient: '#ea580c 0%, #c2410c 100%',
215
+ accentBg: '#ffedd5',
216
+ accentText: '#9a3412',
217
+ },
218
+ changed: {
219
+ emoji: '📊',
220
+ label: 'UCP Profile Updated',
221
+ gradient: '#2563eb 0%, #1d4ed8 100%',
222
+ accentBg: '#dbeafe',
223
+ accentText: '#1e40af',
224
+ },
225
+ };
226
+ const c = config[severity] || config.changed;
227
+ const subject = `${c.emoji} ${domain}: ${c.label}`;
228
+ const scoreSection = oldScore !== null ? `
229
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
230
+ <div style="display: inline-block; margin: 0 20px;">
231
+ <div style="font-size: 14px; color: #666; margin-bottom: 4px;">Previous</div>
232
+ <div style="font-size: 32px; font-weight: bold; color: #666;">${oldScore}</div>
233
+ <div style="font-size: 18px; font-weight: bold; color: ${getGradeColor(oldGrade || 'F')};">Grade ${oldGrade || '?'}</div>
234
+ </div>
235
+ <div style="display: inline-block; font-size: 24px; color: #999; margin: 0 10px;">→</div>
236
+ <div style="display: inline-block; margin: 0 20px;">
237
+ <div style="font-size: 14px; color: #666; margin-bottom: 4px;">Current</div>
238
+ <div style="font-size: 32px; font-weight: bold; color: ${getGradeColor(newGrade)};">${newScore}</div>
239
+ <div style="font-size: 18px; font-weight: bold; color: ${getGradeColor(newGrade)};">Grade ${newGrade}</div>
240
+ </div>
241
+ </div>` : '';
242
+ const changesSection = changes.length > 0 ? `
243
+ <div style="background: white; border-radius: 8px; padding: 16px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
244
+ <h3 style="margin: 0 0 12px 0; font-size: 15px; color: #374151;">What changed</h3>
245
+ ${changes.slice(0, 10).map(ch => `
246
+ <div style="padding: 6px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px;">
247
+ <strong style="color: ${ch.type === 'removed' ? '#dc2626' : ch.type === 'added' ? '#16a34a' : '#2563eb'};">
248
+ ${ch.type === 'removed' ? '−' : ch.type === 'added' ? '+' : '~'}
249
+ </strong>
250
+ ${ch.field}${ch.oldValue !== undefined && ch.newValue !== undefined ? `: ${ch.oldValue} → ${ch.newValue}` : ''}
251
+ </div>
252
+ `).join('')}
253
+ ${changes.length > 10 ? `<div style="padding: 8px 0; font-size: 13px; color: #6b7280;">...and ${changes.length - 10} more changes</div>` : ''}
254
+ </div>` : '';
255
+ const html = `
256
+ <!DOCTYPE html>
257
+ <html>
258
+ <head>
259
+ <meta charset="utf-8">
260
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
261
+ <title>${c.label}</title>
262
+ </head>
263
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
264
+ ${getEmailHeader(c.label, c.gradient)}
265
+
266
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
267
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
268
+
269
+ <div style="background: ${c.accentBg}; color: ${c.accentText}; padding: 12px 16px; border-radius: 8px; margin: 16px 0; font-weight: 500;">
270
+ ${summary}
271
+ </div>
272
+
273
+ ${scoreSection}
274
+ ${changesSection}
275
+
276
+ <p style="text-align: center; margin: 30px 0;">
277
+ <a href="${dashboardUrl}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, ${c.gradient}); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">View Full Report</a>
278
+ </p>
279
+
280
+ ${getEmailFooter()}
281
+ </div>
282
+ </body>
283
+ </html>
284
+ `;
285
+ const text = `${c.label}
286
+
287
+ Hi${to.name ? ` ${to.name}` : ''},
288
+
289
+ ${summary}
290
+
291
+ ${oldScore !== null ? `Score: ${oldScore} (${oldGrade}) → ${newScore} (${newGrade})` : `Score: ${newScore} (${newGrade})`}
292
+
293
+ ${changes.length > 0 ? 'Changes:\n' + changes.slice(0, 10).map(ch => ` ${ch.type === 'removed' ? '-' : ch.type === 'added' ? '+' : '~'} ${ch.field}`).join('\n') : ''}
294
+
295
+ View full report: ${dashboardUrl}
296
+
297
+ ---
298
+ UCP Tools - AI Commerce Readiness Platform
299
+ `;
300
+ return sendEmail({
301
+ to: [to],
302
+ subject,
303
+ html,
304
+ text,
305
+ });
306
+ }
307
+ /**
308
+ * Send a welcome email to new subscribers
309
+ */
310
+ async function sendWelcomeEmail(params) {
311
+ const { to, dashboardUrl, trialEndsAt } = params;
312
+ const trialEndDate = trialEndsAt
313
+ ? new Date(trialEndsAt).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
314
+ : null;
315
+ const subject = 'Welcome to UCP Tools — your 7-day free trial starts now!';
316
+ const html = `
317
+ <!DOCTYPE html>
318
+ <html>
319
+ <head>
320
+ <meta charset="utf-8">
321
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
322
+ </head>
323
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
324
+ ${getEmailHeader('Your 7-Day Free Trial Has Started!')}
325
+
326
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
327
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
328
+
329
+ <p>Welcome to UCP Tools! Your free trial is active${trialEndDate ? ` until <strong>${trialEndDate}</strong>` : ' for the next 7 days'}. No credit card required.</p>
330
+
331
+ <p><strong>In 7 days, you'll know exactly which AI agents are discovering your store and whether they can complete purchases.</strong></p>
332
+
333
+ <p>Here's what we'll measure for you:</p>
334
+
335
+ <ul style="padding-left: 20px;">
336
+ <li><strong>AI Discoverability Dashboard</strong> &mdash; Are agents finding you? Which ones? How often?</li>
337
+ <li><strong>Compliance &rarr; Traffic Correlation</strong> &mdash; See if your UCP improvements drive real visits</li>
338
+ <li><strong>Conversion Funnel</strong> &mdash; Discovery &gt; Browse &gt; Checkout &gt; Payment &gt; Order</li>
339
+ <li><strong>Break Detection</strong> &mdash; Know instantly when something stops working</li>
340
+ </ul>
341
+
342
+ <div style="background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 16px; margin: 20px 0;">
343
+ <p style="margin: 0; font-size: 14px; color: #0369a1;"><strong>Over the next 7 days, we'll send you:</strong></p>
344
+ <ul style="margin: 8px 0 0 0; padding-left: 20px; font-size: 14px; color: #0369a1;">
345
+ <li>Day 1: Set up analytics tracking (15 min) &mdash; start collecting data immediately</li>
346
+ <li>Day 2: Your personalized AI commerce report</li>
347
+ <li>Day 6: Your trial results &mdash; what the data shows</li>
348
+ </ul>
349
+ </div>
350
+
351
+ <p style="text-align: center; margin: 30px 0;">
352
+ <a href="${dashboardUrl}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #36B5A2 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">Go to Dashboard</a>
353
+ </p>
354
+
355
+ ${getEmailFooter()}
356
+ </div>
357
+ </body>
358
+ </html>
359
+ `;
360
+ const text = `
361
+ Your 7-Day Free Trial Has Started!
362
+
363
+ Hi${to.name ? ` ${to.name}` : ''},
364
+
365
+ Welcome to UCP Tools! Your free trial is active${trialEndDate ? ` until ${trialEndDate}` : ' for the next 7 days'}. No credit card required.
366
+
367
+ In 7 days, you'll know exactly which AI agents are discovering your store and whether they can complete purchases.
368
+
369
+ Here's what we'll measure for you:
370
+
371
+ - AI Discoverability Dashboard - Are agents finding you? Which ones? How often?
372
+ - Compliance > Traffic Correlation - See if your UCP improvements drive real visits
373
+ - Conversion Funnel - Discovery > Browse > Checkout > Payment > Order
374
+ - Break Detection - Know instantly when something stops working
375
+
376
+ Over the next 7 days, we'll send you:
377
+ - Day 1: Set up analytics tracking (15 min) - start collecting data immediately
378
+ - Day 2: Your personalized AI commerce report
379
+ - Day 6: Your trial results - what the data shows
380
+
381
+ Go to your dashboard: ${dashboardUrl}
382
+
383
+ ---
384
+ UCP Tools - AI Commerce Readiness Platform
385
+ Questions? Reply to this email - we read everything.
386
+ `;
387
+ return sendEmail({
388
+ to: [to],
389
+ subject,
390
+ html,
391
+ text,
392
+ });
393
+ }
394
+ /**
395
+ * Send a password reset email
396
+ */
397
+ async function sendPasswordResetEmail(params) {
398
+ const { to, resetUrl } = params;
399
+ const subject = 'Reset your UCP Tools password';
400
+ const html = `
401
+ <!DOCTYPE html>
402
+ <html>
403
+ <head>
404
+ <meta charset="utf-8">
405
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
406
+ <title>Password Reset</title>
407
+ </head>
408
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
409
+ ${getEmailHeader('Password Reset', '#2E86AB 0%, #1a5c7a 100%')}
410
+
411
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
412
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
413
+
414
+ <p>We received a request to reset your password for your UCP Tools account.</p>
415
+
416
+ <p style="text-align: center; margin: 30px 0;">
417
+ <a href="${resetUrl}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #1a5c7a 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">Reset Password</a>
418
+ </p>
419
+
420
+ <p>This link will expire in <strong>1 hour</strong>.</p>
421
+
422
+ <p>If you didn't request this password reset, you can safely ignore this email. Your password will remain unchanged.</p>
423
+
424
+ ${getEmailFooter()}
425
+
426
+ <p style="font-size: 12px; color: #666; margin-top: 16px;">
427
+ If the button doesn't work, copy and paste this link into your browser:<br>
428
+ <a href="${resetUrl}" style="color: #2E86AB; word-break: break-all;">${resetUrl}</a>
429
+ </p>
430
+ </div>
431
+ </body>
432
+ </html>
433
+ `;
434
+ const text = `
435
+ Password Reset
436
+
437
+ Hi${to.name ? ` ${to.name}` : ''},
438
+
439
+ We received a request to reset your password for your UCP Tools account.
440
+
441
+ Reset your password by visiting this link:
442
+ ${resetUrl}
443
+
444
+ This link will expire in 1 hour.
445
+
446
+ If you didn't request this password reset, you can safely ignore this email. Your password will remain unchanged.
447
+
448
+ ---
449
+ UCP Tools - AI Commerce Readiness Platform
450
+ `;
451
+ return sendEmail({
452
+ to: [to],
453
+ subject,
454
+ html,
455
+ text,
456
+ });
457
+ }
458
+ /**
459
+ * Send trial Day 2 email - Personalized Domain Report
460
+ * Shows actual scan results to pull users back after session 1 churn
461
+ */
462
+ async function sendTrialDay2Email(params) {
463
+ const { to, dashboardUrl, domain, score, grade, topIssues = [] } = params;
464
+ const hasDomain = domain && score !== undefined && grade;
465
+ const subject = hasDomain
466
+ ? `Your store scored ${grade} (${score}/100) — here's what to fix`
467
+ : 'Your AI commerce readiness report is waiting';
468
+ const domainReportSection = hasDomain ? `
469
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
470
+ <div style="font-size: 14px; color: #666; margin-bottom: 8px;">${domain}</div>
471
+ <div style="font-size: 48px; font-weight: bold; color: ${getGradeColor(grade)};">${grade}</div>
472
+ <div style="font-size: 24px; font-weight: bold; color: #333; margin-top: 4px;">${score}/100</div>
473
+ </div>
474
+ ${topIssues.length > 0 ? `
475
+ <div style="background: white; border-radius: 8px; padding: 16px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
476
+ <h3 style="margin: 0 0 12px 0; font-size: 15px; color: #374151;">Top issues to fix</h3>
477
+ ${topIssues.slice(0, 5).map(issue => `
478
+ <div style="padding: 6px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px;">
479
+ ${issue.severity === 'error' ? '❌' : '⚠️'} ${issue.message}
480
+ </div>
481
+ `).join('')}
482
+ </div>` : ''}
483
+ ` : `
484
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
485
+ <p style="font-size: 16px; color: #333; margin: 0;">Add your store domain to get a personalized AI commerce report.</p>
486
+ </div>
487
+ `;
488
+ const html = `
489
+ <!DOCTYPE html>
490
+ <html>
491
+ <head>
492
+ <meta charset="utf-8">
493
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
494
+ </head>
495
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
496
+ ${getEmailHeader('Your AI Commerce Report')}
497
+
498
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
499
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
500
+
501
+ ${hasDomain
502
+ ? `<p>Yesterday you checked <strong>${domain}</strong>. Here's your AI commerce readiness snapshot:</p>`
503
+ : `<p>You signed up yesterday but haven't added a domain yet. Add your store to see how AI shopping agents experience it:</p>`}
504
+
505
+ ${domainReportSection}
506
+
507
+ <div style="background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 16px; margin: 20px 0;">
508
+ <p style="margin: 0; font-size: 14px; color: #0369a1;">
509
+ <strong>What happens next?</strong> We'll automatically re-scan your domain every week and email you if anything changes. Your next scan is in ~6 days.
510
+ </p>
511
+ </div>
512
+
513
+ <p style="text-align: center; margin: 30px 0;">
514
+ <a href="${hasDomain ? dashboardUrl : dashboardUrl + '?welcome=true'}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #36B5A2 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">${hasDomain ? 'View Full Report' : 'Add Your Domain'}</a>
515
+ </p>
516
+
517
+ <p style="text-align: center; font-size: 13px; color: #666; margin-bottom: 20px;">
518
+ You have <strong>5 days</strong> left in your free trial.
519
+ <a href="https://ucptools.dev/dashboard/subscription" style="color: #2E86AB;">Manage subscription</a>
520
+ </p>
521
+
522
+ ${getEmailFooter()}
523
+ </div>
524
+ </body>
525
+ </html>
526
+ `;
527
+ const text = `Your AI Commerce Report
528
+
529
+ Hi${to.name ? ` ${to.name}` : ''},
530
+
531
+ ${hasDomain
532
+ ? `Yesterday you checked ${domain}. Here's your snapshot:\n\nGrade: ${grade} (${score}/100)\n${topIssues.length > 0 ? '\nTop issues:\n' + topIssues.slice(0, 5).map(i => `- ${i.message}`).join('\n') : ''}`
533
+ : `You signed up yesterday but haven't added a domain yet. Add your store to see your AI commerce readiness.`}
534
+
535
+ What happens next? We'll automatically re-scan your domain every week and email you if anything changes.
536
+
537
+ ${hasDomain ? `View full report: ${dashboardUrl}` : `Add your domain: ${dashboardUrl}?welcome=true`}
538
+
539
+ ---
540
+ You have 5 days left in your free trial.
541
+ UCP Tools - AI Commerce Readiness Platform
542
+ Questions? Reply to this email - we read everything.
543
+ `;
544
+ return sendEmail({
545
+ to: [to],
546
+ subject,
547
+ html,
548
+ text,
549
+ });
550
+ }
551
+ /**
552
+ * Send trial Day 1 email - Quick Start Guide
553
+ */
554
+ async function sendTrialDay1Email(params) {
555
+ const { to, dashboardUrl } = params;
556
+ const subject = 'Day 1: Start measuring your AI discoverability (15 min setup)';
557
+ const html = `
558
+ <!DOCTYPE html>
559
+ <html>
560
+ <head>
561
+ <meta charset="utf-8">
562
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
563
+ </head>
564
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
565
+ ${getEmailHeader('Start Measuring AI Discoverability')}
566
+
567
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
568
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
569
+
570
+ <p>The sooner you set up measurement, the more data you'll have by the end of your trial. Here's how to start collecting AI agent traffic data today:</p>
571
+
572
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
573
+ <h3 style="margin-top: 0; color: #2E86AB;">1. Install the tracking snippet (15 min)</h3>
574
+ <p style="margin-bottom: 0;">Add our middleware to your server to start measuring AI agent traffic. We have ready-made code for <strong>Next.js, Express, PHP, Python, and Shopify</strong>. <a href="https://ucptools.dev/dashboard/analytics/setup" style="color: #2E86AB;">Set up tracking &rarr;</a></p>
575
+ </div>
576
+
577
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
578
+ <h3 style="margin-top: 0; color: #36B5A2;">2. Run your first AI Commerce check</h3>
579
+ <p style="margin-bottom: 0;">Enter your store URL on our <a href="https://ucptools.dev" style="color: #2E86AB;">homepage</a> and get your AI Commerce Score in 30 seconds. This is your compliance baseline &mdash; analytics will show if improving it drives real traffic.</p>
580
+ </div>
581
+
582
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
583
+ <h3 style="margin-top: 0; color: #2E86AB;">3. Explore your measurement dashboard</h3>
584
+ <p style="margin-bottom: 0;">Even before real data arrives, <a href="https://ucptools.dev/dashboard/analytics?demo=true" style="color: #2E86AB;">explore the demo dashboard</a> to see what you'll be measuring: agent breakdown, conversion funnel, commerce KPIs.</p>
585
+ </div>
586
+
587
+ <p style="text-align: center; margin: 30px 0;">
588
+ <a href="https://ucptools.dev/dashboard/analytics/setup" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #36B5A2 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">Set Up Analytics Tracking</a>
589
+ </p>
590
+
591
+ <p style="text-align: center; font-size: 13px; color: #666; margin-bottom: 20px;">
592
+ You have <strong>6 days</strong> left in your free trial.
593
+ <a href="${dashboardUrl}/subscription" style="color: #2E86AB;">Manage subscription</a>
594
+ </p>
595
+
596
+ ${getEmailFooter()}
597
+ </div>
598
+ </body>
599
+ </html>
600
+ `;
601
+ const text = `
602
+ Start Measuring AI Discoverability
603
+
604
+ Hi${to.name ? ` ${to.name}` : ''},
605
+
606
+ The sooner you set up measurement, the more data you'll have by the end of your trial.
607
+
608
+ 1. INSTALL THE TRACKING SNIPPET (15 MIN)
609
+ Add our middleware to start measuring AI agent traffic. Ready-made code for Next.js, Express, PHP, Python, and Shopify.
610
+ Set up tracking: https://ucptools.dev/dashboard/analytics/setup
611
+
612
+ 2. RUN YOUR FIRST AI COMMERCE CHECK
613
+ Enter your store URL at https://ucptools.dev and get your baseline score in 30 seconds.
614
+
615
+ 3. EXPLORE YOUR MEASUREMENT DASHBOARD
616
+ See demo data showing what you'll measure: agent breakdown, conversion funnel, commerce KPIs.
617
+ Demo dashboard: https://ucptools.dev/dashboard/analytics?demo=true
618
+
619
+ ---
620
+ You have 6 days left in your free trial.
621
+ UCP Tools - AI Commerce Readiness Platform
622
+ Questions? Reply to this email - we read everything.
623
+ `;
624
+ return sendEmail({
625
+ to: [to],
626
+ subject,
627
+ html,
628
+ text,
629
+ });
630
+ }
631
+ /**
632
+ * Send trial Day 3 email - Value reminder + Feature highlight
633
+ */
634
+ async function sendTrialDay3Email(params) {
635
+ const { to, dashboardUrl, domainsAdded = 0 } = params;
636
+ const subject = domainsAdded > 0 ? 'Your first 3 days of AI agent data' : 'Day 3: You\'re missing AI agent data — here\'s how to fix that';
637
+ const html = `
638
+ <!DOCTYPE html>
639
+ <html>
640
+ <head>
641
+ <meta charset="utf-8">
642
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
643
+ </head>
644
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
645
+ ${getEmailHeader('Day 3: Your Measurement Check-In', '#2E86AB 0%, #47C97A 100%')}
646
+
647
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
648
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
649
+
650
+ ${domainsAdded > 0 ? `
651
+ <p>You're monitoring <strong>${domainsAdded} domain${domainsAdded > 1 ? 's' : ''}</strong>. Have you checked your analytics dashboard yet? Even a few days of data can tell you a lot:</p>
652
+
653
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
654
+ <h3 style="margin-top: 0; color: #2E86AB;">What to look for in your dashboard</h3>
655
+ <ul style="margin-bottom: 0; padding-left: 20px;">
656
+ <li><strong>Agent breakdown</strong> &mdash; Which AI agents are finding your store?</li>
657
+ <li><strong>Conversion funnel</strong> &mdash; Are they getting past discovery to browsing and checkout?</li>
658
+ <li><strong>Error analysis</strong> &mdash; Are any agents hitting errors that block purchases?</li>
659
+ <li><strong>Capability gaps</strong> &mdash; What do agents request that you don't support yet?</li>
660
+ </ul>
661
+ </div>
662
+
663
+ <p>This is the data that answers <em>"is my UCP compliance work actually driving results?"</em> &mdash; the question your team (or clients) will ask.</p>
664
+ ` : `
665
+ <p>You're 3 days in, but you haven't set up analytics tracking yet. That means you're missing data on which AI agents are discovering your store.</p>
666
+
667
+ <div style="background: #fff5f5; border: 1px solid #fecaca; border-radius: 8px; padding: 16px; margin: 20px 0;">
668
+ <p style="margin: 0; font-size: 14px; color: #991b1b;"><strong>Why this matters:</strong> Without measurement, you can't know if your UCP compliance work is driving real agent traffic. Compliance scoring alone doesn't close the loop &mdash; you need traffic data to prove ROI.</p>
669
+ </div>
670
+
671
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
672
+ <h3 style="margin-top: 0; color: #2E86AB;">Set up in 15 minutes</h3>
673
+ <p style="margin-bottom: 0;">Add our tracking middleware to your server. We have ready-made code for Next.js, Express, PHP, Python, and Shopify. <a href="https://ucptools.dev/dashboard/analytics/setup" style="color: #2E86AB;">Get started &rarr;</a></p>
674
+ </div>
675
+ `}
676
+
677
+ <p style="text-align: center; margin: 30px 0;">
678
+ <a href="${domainsAdded > 0 ? dashboardUrl + '/analytics' : 'https://ucptools.dev/dashboard/analytics/setup'}" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #47C97A 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">${domainsAdded > 0 ? 'View Your Analytics' : 'Set Up Analytics Tracking'}</a>
679
+ </p>
680
+
681
+ <p style="text-align: center; font-size: 13px; color: #666; margin-bottom: 20px;">
682
+ You have <strong>4 days</strong> left in your trial.
683
+ <a href="https://ucptools.dev/dashboard/subscription" style="color: #2E86AB;">Manage subscription</a>
684
+ </p>
685
+
686
+ ${getEmailFooter()}
687
+ </div>
688
+ </body>
689
+ </html>
690
+ `;
691
+ const text = `
692
+ Day 3: Your Measurement Check-In
693
+
694
+ Hi${to.name ? ` ${to.name}` : ''},
695
+
696
+ ${domainsAdded > 0
697
+ ? `You're monitoring ${domainsAdded} domain${domainsAdded > 1 ? 's' : ''}. Check your analytics dashboard to see:
698
+
699
+ - Agent breakdown: Which AI agents are finding your store?
700
+ - Conversion funnel: Are they getting past discovery to checkout?
701
+ - Error analysis: Are agents hitting errors that block purchases?
702
+ - Capability gaps: What do agents request that you don't support?
703
+
704
+ This data answers "is my UCP compliance work actually driving results?"
705
+
706
+ View your analytics: ${dashboardUrl}/analytics`
707
+ : `You're 3 days in, but you haven't set up analytics tracking yet. Without measurement, you can't know if your UCP compliance work is driving real agent traffic.
708
+
709
+ Set up in 15 minutes - we have ready-made code for Next.js, Express, PHP, Python, and Shopify.
710
+
711
+ Set up tracking: https://ucptools.dev/dashboard/analytics/setup`}
712
+
713
+ ---
714
+ You have 4 days left in your trial.
715
+ UCP Tools - AI Commerce Readiness Platform
716
+ Questions? Reply to this email - we read everything.
717
+ `;
718
+ return sendEmail({
719
+ to: [to],
720
+ subject,
721
+ html,
722
+ text,
723
+ });
724
+ }
725
+ /**
726
+ * Send trial Day 6 email - Trial ending + Conversion CTA
727
+ */
728
+ async function sendTrialDay6Email(params) {
729
+ const { to, dashboardUrl, domainsAdded = 0, validationsRun = 0 } = params;
730
+ const subject = 'Your trial ends tomorrow – here\'s what you\'ll lose ⏰';
731
+ const html = `
732
+ <!DOCTYPE html>
733
+ <html>
734
+ <head>
735
+ <meta charset="utf-8">
736
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
737
+ </head>
738
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
739
+ ${getEmailHeader('⏰ Your Trial Ends Tomorrow', '#f97316 0%, #dc2626 100%')}
740
+
741
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
742
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
743
+
744
+ <p>Your 7-day trial ends tomorrow. After that, your dashboard becomes read-only and all monitoring services will pause:</p>
745
+
746
+ <div style="background: #fff5f5; border: 1px solid #fecaca; border-radius: 8px; padding: 20px; margin: 20px 0;">
747
+ <ul style="margin: 0; padding-left: 20px;">
748
+ <li><strong>Domain monitoring</strong>${domainsAdded > 0 ? ` (${domainsAdded} domain${domainsAdded > 1 ? 's' : ''} currently tracked)` : ''} &mdash; paused</li>
749
+ <li><strong>Weekly auto-validation</strong>${validationsRun > 0 ? ` (${validationsRun} validation${validationsRun > 1 ? 's' : ''} run so far)` : ''} &mdash; stopped</li>
750
+ <li><strong>AI agent analytics</strong> &mdash; no new data</li>
751
+ <li><strong>Score change alerts</strong> &mdash; disabled</li>
752
+ <li><strong>Dashboard access</strong> &mdash; read-only, content blurred</li>
753
+ </ul>
754
+ </div>
755
+
756
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
757
+ <p style="margin-top: 0; font-size: 14px; color: #666;">Continue with the Starter plan</p>
758
+ <p style="font-size: 32px; font-weight: bold; color: #2E86AB; margin: 8px 0;">$9<span style="font-size: 16px; font-weight: normal;">/month</span></p>
759
+ <p style="margin-bottom: 0; font-size: 14px; color: #666;">Monitor up to 3 domains &bull; Cancel anytime</p>
760
+ </div>
761
+
762
+ <p style="text-align: center; margin: 30px 0;">
763
+ <a href="${dashboardUrl}/subscription" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #47C97A 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">Subscribe Now</a>
764
+ </p>
765
+
766
+ ${getEmailFooter()}
767
+ </div>
768
+ </body>
769
+ </html>
770
+ `;
771
+ const text = `
772
+ Your Trial Ends Tomorrow
773
+
774
+ Hi${to.name ? ` ${to.name}` : ''},
775
+
776
+ Your 7-day trial ends tomorrow. After that, your dashboard becomes read-only and all monitoring services will pause:
777
+
778
+ - Domain monitoring${domainsAdded > 0 ? ` (${domainsAdded} domain${domainsAdded > 1 ? 's' : ''} currently tracked)` : ''} - paused
779
+ - Weekly auto-validation${validationsRun > 0 ? ` (${validationsRun} validation${validationsRun > 1 ? 's' : ''} run so far)` : ''} - stopped
780
+ - AI agent analytics - no new data
781
+ - Score change alerts - disabled
782
+ - Dashboard access - read-only, content blurred
783
+
784
+ STARTER PLAN: $9/month
785
+ Monitor up to 3 domains - Cancel anytime
786
+
787
+ Subscribe now: ${dashboardUrl}/subscription
788
+
789
+ ---
790
+ UCP Tools - AI Commerce Readiness Platform
791
+ Questions? Reply to this email - we read everything.
792
+ `;
793
+ return sendEmail({
794
+ to: [to],
795
+ subject,
796
+ html,
797
+ text,
798
+ });
799
+ }
800
+ /**
801
+ * Send trial expired email - Trial has ended, prompt to upgrade
802
+ */
803
+ async function sendTrialExpiredEmail(params) {
804
+ const { to, dashboardUrl, domainsAdded = 0 } = params;
805
+ const subject = 'Your free trial has ended \u2014 upgrade to keep your data';
806
+ const html = `
807
+ <!DOCTYPE html>
808
+ <html>
809
+ <head>
810
+ <meta charset="utf-8">
811
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
812
+ </head>
813
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
814
+ ${getEmailHeader('Your Trial Has Ended', '#dc2626 0%, #991b1b 100%')}
815
+
816
+ <div style="background: #f8f9fa; padding: 30px; border-radius: 0 0 12px 12px; border: 1px solid #e9ecef; border-top: none;">
817
+ <p style="margin-top: 0;">Hi${to.name ? ` ${to.name}` : ''},</p>
818
+
819
+ <p>Your 7-day free trial has ended. Your dashboard access has been paused, but <strong>your domains are still being monitored</strong>:</p>
820
+
821
+ <div style="background: #fff5f5; border: 1px solid #fecaca; border-radius: 8px; padding: 20px; margin: 20px 0;">
822
+ <ul style="margin: 0; padding-left: 20px;">
823
+ <li><strong>Domain monitoring</strong>${domainsAdded > 0 ? ` (${domainsAdded} domain${domainsAdded > 1 ? 's' : ''})` : ''} &mdash; still running</li>
824
+ <li><strong>Dashboard &amp; analytics</strong> &mdash; blurred until you upgrade</li>
825
+ <li><strong>Score change alerts</strong> &mdash; paused</li>
826
+ <li><strong>All your data</strong> &mdash; safe and collecting</li>
827
+ </ul>
828
+ </div>
829
+
830
+ <p>Upgrade now and everything unlocks instantly &mdash; no data gap, no setup needed.</p>
831
+
832
+ <div style="background: white; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
833
+ <p style="margin-top: 0; font-size: 14px; color: #666;">Resume with the Starter plan</p>
834
+ <p style="font-size: 32px; font-weight: bold; color: #2E86AB; margin: 8px 0;">$9<span style="font-size: 16px; font-weight: normal;">/month</span></p>
835
+ <p style="margin-bottom: 0; font-size: 14px; color: #666;">Monitor up to 3 domains &bull; Cancel anytime</p>
836
+ </div>
837
+
838
+ <p style="text-align: center; margin: 30px 0;">
839
+ <a href="${dashboardUrl}/subscription" style="display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #2E86AB 0%, #47C97A 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">Upgrade Now</a>
840
+ </p>
841
+
842
+ ${getEmailFooter()}
843
+ </div>
844
+ </body>
845
+ </html>
846
+ `;
847
+ const text = `Your Trial Has Ended
848
+
849
+ Hi${to.name ? ` ${to.name}` : ''},
850
+
851
+ Your 7-day free trial has ended. Your account has been downgraded to the free tier:
852
+
853
+ - Domain monitoring${domainsAdded > 0 ? ` (${domainsAdded} domain${domainsAdded > 1 ? 's' : ''})` : ''} - paused
854
+ - AI agent analytics - no longer collecting data
855
+ - Score change alerts - disabled
856
+ - Dashboard - analytics blurred
857
+
858
+ Your existing data is still safe. Upgrade now and everything picks up right where you left off.
859
+
860
+ STARTER PLAN: $9/month
861
+ Monitor up to 3 domains - Cancel anytime
862
+
863
+ Upgrade now: ${dashboardUrl}/subscription
864
+
865
+ ---
866
+ UCP Tools - AI Commerce Readiness Platform
867
+ Questions? Reply to this email - we read everything.
868
+ `;
869
+ return sendEmail({
870
+ to: [to],
871
+ subject,
872
+ html,
873
+ text,
874
+ });
875
+ }
876
+ //# sourceMappingURL=email.js.map