git-paid 2.1.6 → 2.1.8

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.
package/bin/git-paid.js CHANGED
@@ -46,10 +46,10 @@ async function runCli() {
46
46
  console.log(" Usage:");
47
47
  console.log(" npx git-paid <url> Run free 8-point SEO/paywall audit & 5-directory preview");
48
48
  console.log(" git paid login <pb_live_key> Authenticate CLI & MCP with your Launch Pass license");
49
- console.log(" git paid fix [url] [PRO] Write 25 pSEO HTML pages, robots.txt, sitemap.xml & llms.txt");
49
+ console.log(" git paid fix [url] [PRO] Write starter pSEO pages (.postbuild/pseo), robots.txt, sitemap.xml & llms.txt");
50
50
  console.log(" git paid submit [url] [PRO] Headless 85-directory form submitter + OTP auto-verifier");
51
- console.log(" git paid inbox [bot-email] [PRO] Check & auto-verify <domain-tld>-bot@postbuild.dev (e.g. acme-com-bot@) links & OTPs");
52
- console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.6)");
51
+ console.log(" git paid inbox [bot-email] [PRO] Check <domain-tld>-bot@postbuild.dev inbox (pass --verify to auto-click links)");
52
+ console.log(" git paid mcp Start Postbuild MCP Server (postbuild-mcp v2.1.7)");
53
53
  console.log(" git paid init-agent Scaffold .agent/skills/postbuild/SKILL.md & MCP config");
54
54
  console.log(" git paid vault [path] [PRO] Sync live telemetry & Bento dashboard to Obsidian VaultOS\n");
55
55
  process.exit(0);
@@ -153,7 +153,7 @@ async function runCli() {
153
153
  if (subcmd === "init-agent" || subcmd === "agent" || subcmd === "skill") {
154
154
  const res = installAgentSkillFiles({ cwd: process.cwd() });
155
155
  console.log(` [+] Scaffolded Agent Skill: ${res.skillMdPath}`);
156
- console.log(` [+] Powered by: postbuild-mcp@2.1.6 (Stateful Delta Compaction)\n`);
156
+ console.log(` [+] Powered by: postbuild-mcp@2.1.7 (Stateful Delta Compaction)\n`);
157
157
  console.log(" Add this to your Claude Code / Cursor / Antigravity MCP config:\n");
158
158
  console.log(JSON.stringify(res.mcpConfigSnippet, null, 2));
159
159
  console.log("");
@@ -213,7 +213,15 @@ async function runCli() {
213
213
  if (args.includes("--test")) {
214
214
  await sendTestVerificationEmail(inboxAddr, "postbuild.dev");
215
215
  }
216
- const res = await autoVerifyInboxLinks(inboxAddr, { autoVerify: !args.includes("--no-verify") });
216
+ const res = await autoVerifyInboxLinks(inboxAddr, {
217
+ autoVerify: args.includes("--verify"),
218
+ bearerToken: license.token
219
+ });
220
+ if (res.requiresDomainVerification) {
221
+ console.log(` [!] Domain ownership verification required for ${license.domain || inboxAddr}:`);
222
+ console.log(` ${res.error || "Verify ownership on https://www.postbuild.dev/?tab=account to unlock your inbox."}\n`);
223
+ process.exit(1);
224
+ }
217
225
  console.log(
218
226
  ` [*] Verification Inbox: ${res.inboxAddress} (${res.totalMessages} messages, ${res.verifiedCount} auto-verified)\n`
219
227
  );
@@ -277,7 +285,9 @@ async function runCli() {
277
285
 
278
286
  const fixResult = applyLocalSeoFixes(report, {
279
287
  cwd: process.cwd(),
280
- overwritePublic: isFixMode || args.includes("--force")
288
+ overwritePublic: args.includes("--force"),
289
+ publishPseoToPublic: args.includes("--publish-pages"),
290
+ verificationToken: license.verificationToken || ""
281
291
  });
282
292
 
283
293
  console.log(`\n --- Local Repository Auto-Fix (${fixResult.framework} — Licensed to ${license.email}) ---`);
package/lib/auth.js CHANGED
@@ -10,19 +10,178 @@ const otpChallengeState = new Map(); // cid -> { attempts: number, used: boolean
10
10
  const memoryEntitlements = new Map(); // key (jti or email) -> { jti, email, customerId, plan, domain, inbox, status, expiresAt }
11
11
  const processedWebhookEvents = new Set(); // stripe_event_id idempotency set
12
12
 
13
+ export const FREE_EMAIL_PROVIDERS = new Set([
14
+ "gmail.com",
15
+ "googlemail.com",
16
+ "outlook.com",
17
+ "outlook.de",
18
+ "outlook.fr",
19
+ "outlook.es",
20
+ "outlook.co.uk",
21
+ "hotmail.com",
22
+ "hotmail.co.uk",
23
+ "hotmail.fr",
24
+ "hotmail.de",
25
+ "hotmail.es",
26
+ "hotmail.it",
27
+ "live.com",
28
+ "live.co.uk",
29
+ "live.fr",
30
+ "live.de",
31
+ "msn.com",
32
+ "yahoo.com",
33
+ "yahoo.co.uk",
34
+ "yahoo.ca",
35
+ "yahoo.co.jp",
36
+ "yahoo.fr",
37
+ "yahoo.de",
38
+ "yahoo.es",
39
+ "yahoo.it",
40
+ "yahoo.com.au",
41
+ "yahoo.com.br",
42
+ "yahoo.co.in",
43
+ "ymail.com",
44
+ "icloud.com",
45
+ "me.com",
46
+ "mac.com",
47
+ "aol.com",
48
+ "proton.me",
49
+ "protonmail.com",
50
+ "protonmail.ch",
51
+ "pm.me",
52
+ "zoho.com",
53
+ "gmx.com",
54
+ "gmx.de",
55
+ "gmx.net",
56
+ "gmx.at",
57
+ "gmx.ch",
58
+ "web.de",
59
+ "t-online.de",
60
+ "freenet.de",
61
+ "mail.com",
62
+ "fastmail.com",
63
+ "hey.com",
64
+ "yandex.com",
65
+ "yandex.ru",
66
+ "duck.com",
67
+ "tuta.com",
68
+ "tutanota.com",
69
+ "qq.com",
70
+ "163.com",
71
+ "126.com",
72
+ "sina.com",
73
+ "sohu.com",
74
+ "foxmail.com",
75
+ "naver.com",
76
+ "daum.net",
77
+ "hanmail.net",
78
+ "mail.ru",
79
+ "bk.ru",
80
+ "inbox.ru",
81
+ "list.ru",
82
+ "rambler.ru",
83
+ "seznam.cz",
84
+ "wp.pl",
85
+ "onet.pl",
86
+ "interia.pl",
87
+ "o2.pl",
88
+ "libero.it",
89
+ "virgilio.it",
90
+ "alice.it",
91
+ "orange.fr",
92
+ "wanadoo.fr",
93
+ "free.fr",
94
+ "sfr.fr",
95
+ "laposte.net"
96
+ ]);
97
+
98
+ export function isFreeEmailDomain(emailDomain = "") {
99
+ const clean = String(emailDomain || "").toLowerCase().trim();
100
+ if (!clean) return true;
101
+ if (FREE_EMAIL_PROVIDERS.has(clean)) return true;
102
+ if (/^(gmail|googlemail|yahoo|ymail|hotmail|outlook|live|msn|icloud|aol|proton|protonmail|zoho|gmx|yandex|tuta|tutanota|fastmail)\.[a-z.]{2,6}$/i.test(clean)) {
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+
13
108
  export function requireAuthSecret() {
14
109
  const explicit = (process.env.POSTBUILD_AUTH_SECRET || process.env.POSTBUILD_SECRET || "").trim();
15
110
  if (explicit.length >= 32) {
16
111
  return explicit;
17
112
  }
18
- // Safe domain-separated derivation on Vercel if POSTBUILD_AUTH_SECRET has not been added to project env yet
19
- const resendSeed = (process.env.RESEND_API_KEY || "").trim();
20
- if (process.env.VERCEL && resendSeed.length >= 24 && !process.env.POSTBUILD_STRICT_SECRET_TEST) {
21
- return crypto.createHmac("sha256", resendSeed).update("postbuild:auth:v2:hmac-signing-key").digest("hex");
22
- }
23
113
  throw new Error("POSTBUILD_SECRET or POSTBUILD_AUTH_SECRET is missing or too short (minimum 32 characters required)");
24
114
  }
25
115
 
116
+ export function getDomainVerificationToken(domain = "", email = "") {
117
+ const cleanDomain = String(domain || "")
118
+ .toLowerCase()
119
+ .trim()
120
+ .replace(/^https?:\/\//, "")
121
+ .replace(/^www\./, "")
122
+ .split("/")[0]
123
+ .split(":")[0];
124
+ const cleanEmail = String(email || "").toLowerCase().trim();
125
+ const verifyKey = (process.env.POSTBUILD_VERIFY_SECRET || "").trim();
126
+ const secret = verifyKey.length >= 16 ? verifyKey : requireAuthSecret();
127
+ const digest = crypto
128
+ .createHmac("sha256", secret)
129
+ .update(`domain-verify:${cleanDomain}:${cleanEmail}`)
130
+ .digest("hex")
131
+ .slice(0, 32);
132
+ return `postbuild-verify=${digest}`;
133
+ }
134
+
135
+ export function getCandidateVerificationTokens(domain = "", email = "") {
136
+ const cleanDomain = String(domain || "")
137
+ .toLowerCase()
138
+ .trim()
139
+ .replace(/^https?:\/\//, "")
140
+ .replace(/^www\./, "")
141
+ .split("/")[0]
142
+ .split(":")[0];
143
+ const cleanEmail = String(email || "").toLowerCase().trim();
144
+ const secrets = [
145
+ (process.env.POSTBUILD_VERIFY_SECRET || "").trim(),
146
+ (() => {
147
+ try {
148
+ return requireAuthSecret();
149
+ } catch {
150
+ return "";
151
+ }
152
+ })()
153
+ ].filter((s) => s.length >= 16);
154
+ const tokens = new Set([getDomainVerificationToken(cleanDomain, cleanEmail)]);
155
+ for (const s of secrets) {
156
+ const digest = crypto
157
+ .createHmac("sha256", s)
158
+ .update(`domain-verify:${cleanDomain}:${cleanEmail}`)
159
+ .digest("hex")
160
+ .slice(0, 32);
161
+ tokens.add(`postbuild-verify=${digest}`);
162
+ }
163
+ return Array.from(tokens);
164
+ }
165
+
166
+ export function isSameDomainVerifiedEmail(email = "", domain = "", emailVerified = false) {
167
+ const cleanEmail = String(email || "").toLowerCase().trim();
168
+ const cleanDomain = String(domain || "")
169
+ .toLowerCase()
170
+ .trim()
171
+ .replace(/^https?:\/\//, "")
172
+ .replace(/^www\./, "")
173
+ .split("/")[0]
174
+ .split(":")[0];
175
+ if (!cleanEmail || !cleanDomain || !cleanDomain.includes(".")) return false;
176
+ if (getFounderEmailsSet().has(cleanEmail) && cleanDomain === "postbuild.dev") {
177
+ return true;
178
+ }
179
+ if (!emailVerified) return false;
180
+ const emailDomain = cleanEmail.includes("@") ? cleanEmail.split("@")[1] : "";
181
+ if (!emailDomain || isFreeEmailDomain(emailDomain)) return false;
182
+ return cleanDomain === emailDomain || cleanDomain.endsWith(`.${emailDomain}`);
183
+ }
184
+
26
185
  function timingSafeStringEqual(a, b) {
27
186
  if (typeof a !== "string" || typeof b !== "string") return false;
28
187
  const bufA = Buffer.from(a, "utf8");
@@ -244,6 +403,7 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
244
403
  plan: localCheck.plan,
245
404
  domain: localCheck.domain,
246
405
  inbox: localCheck.inbox,
406
+ verificationToken: getDomainVerificationToken(localCheck.domain, localCheck.email),
247
407
  jti: localCheck.jti,
248
408
  token: candidateToken
249
409
  };
@@ -294,6 +454,7 @@ export async function verifyCliLicense({ domain = "postbuild.dev", token = "" }
294
454
  plan: data.plan,
295
455
  domain: data.domain,
296
456
  inbox: data.inbox,
457
+ verificationToken: data.verificationToken || "",
297
458
  token: candidateToken
298
459
  };
299
460
  }
@@ -320,6 +481,7 @@ export function signLicenseToken({
320
481
  domain = "postbuild.dev",
321
482
  jti = "",
322
483
  customerId = null,
484
+ emailVerified = true,
323
485
  ttlSeconds = 365 * 24 * 3600
324
486
  }) {
325
487
  const secret = requireAuthSecret();
@@ -353,6 +515,7 @@ export function signLicenseToken({
353
515
  plan,
354
516
  domain: cleanDomain,
355
517
  inbox,
518
+ emailVerified: Boolean(emailVerified),
356
519
  iat: now,
357
520
  exp
358
521
  };
@@ -403,7 +566,7 @@ export function verifyLicenseToken(token) {
403
566
  if (!payload.inbox && payload.domain) {
404
567
  payload.inbox = getBotInboxForDomain(payload.domain, payload.email);
405
568
  }
406
- return { valid: true, ...payload };
569
+ return { valid: true, ...payload, emailVerified: Boolean(payload.emailVerified) };
407
570
  } catch {
408
571
  return { valid: false };
409
572
  }
@@ -566,13 +729,17 @@ async function sendLoginOtpViaResend(email, otpCode) {
566
729
  },
567
730
  body: JSON.stringify({
568
731
  from: "postbuild <login@postbuild.dev>",
732
+ reply_to: "support@postbuild.dev",
569
733
  to: recipients,
570
734
  subject: `${otpCode} is your postbuild verification code`,
571
- html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; max-width: 520px; margin: 0 auto; padding: 28px; border: 1px solid #e2e8f0; border-radius: 14px; color: #0f172a;">
572
- <h2 style="margin: 0 0 8px; font-size: 20px;">postbuild · Account Verification</h2>
573
- <p style="color: #475569; font-size: 14px; line-height: 1.6;">Enter this 6-digit verification code on <strong>www.postbuild.dev</strong> within 10 minutes to issue your license key and manage your billing:</p>
574
- <div style="background: #ecfdf5; border: 1px solid #6ee7b7; color: #047857; font-family: monospace; font-size: 28px; font-weight: 800; letter-spacing: 6px; text-align: center; padding: 14px; border-radius: 10px; margin: 18px 0;">${otpCode}</div>
575
- <p style="color: #64748b; font-size: 12px; line-height: 1.5;">For security, your <code>pb_live_</code> license key is issued only after you enter this code.</p>
735
+ html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif; max-width: 520px; margin: 0 auto; padding: 24px; border: 1px solid #e2e8f0; border-radius: 14px; background: #ffffff; color: #0f172a;">
736
+ <div style="margin-bottom: 16px;">
737
+ <span style="display: inline-block; background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 3px 8px; border-radius: 999px;">Security Code</span>
738
+ </div>
739
+ <h2 style="margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #0f172a;">Verify your postbuild sign-in</h2>
740
+ <p style="color: #475569; font-size: 14px; line-height: 1.6; margin: 0 0 18px;">Enter this 6-digit verification code on <strong>postbuild.dev</strong> within 10 minutes to access your license key, verification inbox, and receipts:</p>
741
+ <div style="background: #ecfdf5; border: 1px solid #6ee7b7; color: #047857; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 28px; font-weight: 800; letter-spacing: 6px; text-align: center; padding: 14px; border-radius: 10px; margin: 0 0 18px;">${otpCode}</div>
742
+ <p style="color: #64748b; font-size: 12px; line-height: 1.5; margin: 0;">If you did not request this code, you can safely ignore this email.</p>
576
743
  </div>`
577
744
  })
578
745
  });
@@ -599,9 +766,16 @@ function escapeHtml(str) {
599
766
  export async function sendLicenseFulfillmentEmail({ email, licenseToken, planId, targetDomain }) {
600
767
  const resendKey = (process.env.RESEND_API_KEY || "").trim();
601
768
  if (!resendKey || !email) return { sent: false };
602
- const safeDomain = escapeHtml(targetDomain || "postbuild.dev");
603
- const inbox = escapeHtml(getBotInboxForDomain(targetDomain, email));
769
+ const rawDomain = targetDomain || "postbuild.dev";
770
+ const safeDomain = escapeHtml(rawDomain);
771
+ const inbox = escapeHtml(getBotInboxForDomain(rawDomain, email));
604
772
  const safeToken = escapeHtml(licenseToken);
773
+ const planName = planId === "studio_monthly" ? "postbuild Studio" : "postbuild Launch Pass";
774
+ const badgeLabel = planId === "studio_monthly" ? "Studio Active" : "Launch Pass Active";
775
+ const autoVerifiedByEmail = isSameDomainVerifiedEmail(email, rawDomain, true);
776
+ const inboxStatusNote = autoVerifiedByEmail
777
+ ? "Confirm your email on Account tab (or use key below) to open"
778
+ : "Opens after domain check on Account tab";
605
779
  try {
606
780
  const resp = await fetch("https://api.resend.com/emails", {
607
781
  method: "POST",
@@ -611,15 +785,49 @@ export async function sendLicenseFulfillmentEmail({ email, licenseToken, planId,
611
785
  },
612
786
  body: JSON.stringify({
613
787
  from: "postbuild <login@postbuild.dev>",
788
+ reply_to: "support@postbuild.dev",
614
789
  to: [email],
615
- subject: `Your postbuild (${planId === "studio_monthly" ? "Studio" : "Launch Pass"}) License Key for ${targetDomain}`,
616
- html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; max-width: 540px; margin: 0 auto; padding: 28px; border: 1px solid #e2e8f0; border-radius: 14px; color: #0f172a;">
617
- <h2 style="margin: 0 0 8px; font-size: 20px;">postbuild · License Activated</h2>
618
- <p style="color: #475569; font-size: 14px; line-height: 1.6;">Your <strong>${planId === "studio_monthly" ? "postbuild Studio" : "postbuild Launch Pass"}</strong> for <strong>${safeDomain}</strong> is active.</p>
619
- <p style="color: #475569; font-size: 13px; margin-bottom: 6px;"><strong>Dedicated Verification Inbox:</strong> <code>${inbox}</code></p>
620
- <p style="color: #475569; font-size: 13px; margin-bottom: 6px;"><strong>CLI &amp; Web License Key:</strong></p>
621
- <pre style="background: #f8fafc; border: 1px solid #e2e8f0; padding: 12px; border-radius: 8px; font-size: 11px; overflow-x: auto;">${safeToken}</pre>
622
- <p style="color: #475569; font-size: 13px; margin-top: 12px;">Authenticate your CLI with: <code>npx git-paid login ${safeToken}</code></p>
790
+ subject: `Your ${planName} for ${rawDomain} is active`,
791
+ html: `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif; max-width: 540px; margin: 0 auto; padding: 24px; border: 1px solid #e2e8f0; border-radius: 14px; background: #ffffff; color: #0f172a;">
792
+ <div style="margin-bottom: 14px;">
793
+ <span style="display: inline-block; background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 3px 9px; border-radius: 999px;">${escapeHtml(badgeLabel)}</span>
794
+ </div>
795
+ <h2 style="margin: 0 0 8px; font-size: 20px; font-weight: 700; color: #0f172a;">Your ${escapeHtml(planName)} for ${safeDomain} is ready</h2>
796
+ <p style="color: #475569; font-size: 14px; line-height: 1.6; margin: 0 0 18px;">All 85 startup &amp; AI directories, your SEO Launch Bundle (<code>.zip</code>), and your verification inbox (<code>${inbox}</code>) are included.${autoVerifiedByEmail ? ` Confirm your <strong>@${safeDomain}</strong> email once on your Account tab (or sign in with the license key below) to open <code>${inbox}</code>.` : ` Because your checkout email is not on <strong>@${safeDomain}</strong>, complete the domain ownership check on your Account tab (and keep the verification file or meta tag in place) before submitting to directories so you can read incoming confirmation codes.`}</p>
797
+
798
+ <div style="margin: 0 0 20px;">
799
+ <a href="https://www.postbuild.dev/?tab=account" style="display: inline-block; background: #047857; color: #ffffff; text-decoration: none; font-weight: 600; font-size: 14px; padding: 11px 18px; border-radius: 8px;">Open Account &amp; Download .zip Bundle &rarr;</a>
800
+ </div>
801
+
802
+ <table role="presentation" style="width: 100%; border-collapse: collapse; margin: 0 0 18px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px;">
803
+ <tr>
804
+ <td style="padding: 10px 14px; border-bottom: 1px solid #e2e8f0; font-size: 12px; color: #64748b; font-weight: 600;">Licensed Domain</td>
805
+ <td style="padding: 10px 14px; border-bottom: 1px solid #e2e8f0; font-size: 13px; color: #0f172a; font-weight: 700; text-align: right;">${safeDomain}</td>
806
+ </tr>
807
+ <tr>
808
+ <td style="padding: 10px 14px; font-size: 12px; color: #64748b; font-weight: 600;">Verification Inbox</td>
809
+ <td style="padding: 10px 14px; font-size: 12px; color: #047857; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; text-align: right; word-break: break-all;">${inbox}<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif; font-size: 11px; color: #64748b; font-weight: 500; margin-top: 2px; word-break: normal; overflow-wrap: break-word;">${escapeHtml(inboxStatusNote)}</div></td>
810
+ </tr>
811
+ </table>
812
+
813
+ <div style="border-top: 1px solid #e2e8f0; padding-top: 16px; margin-bottom: 18px;">
814
+ <div style="font-size: 13px; font-weight: 700; color: #0f172a; margin-bottom: 8px;">3-Step Quickstart</div>
815
+ <ol style="margin: 0; padding-left: 18px; color: #475569; font-size: 13px; line-height: 1.6;">
816
+ <li style="margin-bottom: 6px;"><strong>Deploy Your .zip Launch Bundle:</strong> Click <em>Download .zip</em> on your Account tab (includes <code>START_HERE.md</code>, <code>public/.well-known/postbuild-verify.txt</code>, starter HTML SEO pages, and <code>directories-85.csv</code>) and deploy to <code>${safeDomain}</code>.</li>
817
+ <li style="margin-bottom: 6px;"><strong>Verify Domain Ownership:</strong> ${autoVerifiedByEmail ? `Confirm your <code>@${safeDomain}</code> email once on your Account tab (or paste the license key below) to open <code>${inbox}</code>.` : `Click <em>Verify Domain Now</em> on your Account tab after deploying your bundle (or adding the meta tag / DNS TXT record) and keep it in place to unlock <code>${inbox}</code>.`}</li>
818
+ <li><strong>Submit to 85 Directories:</strong> Launch prefilled directory links from the Dispatch tab or <code>directories-85.csv</code> using <code>${inbox}</code> — 6-digit OTPs and confirmation links arrive in your Account Inbox.</li>
819
+ </ol>
820
+ </div>
821
+
822
+ <div style="margin: 0 0 18px;">
823
+ <div style="font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #64748b; margin-bottom: 4px;">CLI &amp; MCP License Key</div>
824
+ <div style="font-size: 12px; color: #64748b; margin-bottom: 6px;">Most buyers never need this — you can sign in with your email at <strong>postbuild.dev</strong>, or copy this key for <code>npx git-paid login</code>:</div>
825
+ <div style="background: #f8fafc; border: 1px solid #cbd5e1; padding: 12px; border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 1.5; color: #0f172a; word-break: break-all;">${safeToken}</div>
826
+ </div>
827
+
828
+ <div style="border-top: 1px solid #e2e8f0; padding-top: 12px; font-size: 12px; color: #64748b; line-height: 1.5;">
829
+ Covered by our 14-day refund guarantee. Questions? Reply to this email or contact <a href="mailto:support@postbuild.dev" style="color: #047857; text-decoration: none; font-weight: 600;">support@postbuild.dev</a>.
830
+ </div>
623
831
  </div>`
624
832
  })
625
833
  });
@@ -705,10 +913,13 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
705
913
  ok: true,
706
914
  authenticated: true,
707
915
  email: verified.email,
916
+ emailVerified: Boolean(verified.emailVerified),
917
+ domainVerified: isSameDomainVerifiedEmail(verified.email, verified.domain, verified.emailVerified),
708
918
  customerId: verified.customerId || null,
709
919
  plan: verified.plan,
710
920
  domain: verified.domain,
711
921
  inbox: verified.inbox,
922
+ verificationToken: getDomainVerificationToken(verified.domain, verified.email),
712
923
  isFounder: founderEmails.has(String(verified.email || "").toLowerCase().trim()),
713
924
  token
714
925
  };
@@ -729,15 +940,18 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
729
940
  const plan = existingEnt?.plan || check.plan || "launch_pass";
730
941
  const domain = existingEnt?.domain || check.domain || "postbuild.dev";
731
942
  const customerId = existingEnt?.customerId || check.customerId || null;
732
- const issued = signLicenseToken({ email: check.email, plan, domain, customerId });
943
+ const issued = signLicenseToken({ email: check.email, plan, domain, customerId, emailVerified: true });
733
944
  return {
734
945
  ok: true,
735
946
  authenticated: true,
736
947
  email: check.email,
948
+ emailVerified: true,
949
+ domainVerified: isSameDomainVerifiedEmail(check.email, domain, true),
737
950
  customerId,
738
951
  plan,
739
952
  domain,
740
953
  inbox: getBotInboxForDomain(domain, check.email),
954
+ verificationToken: getDomainVerificationToken(domain, check.email),
741
955
  isFounder: founderEmails.has(String(check.email || "").toLowerCase().trim()),
742
956
  token: issued
743
957
  };
@@ -749,7 +963,7 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
749
963
  stripe = stripeMod.getStripeClient();
750
964
  } catch {}
751
965
 
752
- // 3. Stripe Checkout return session_id verification
966
+ // 3. Stripe Checkout return session_id verification (checkout email is NOT verified by Stripe)
753
967
  if (sessionId && stripe) {
754
968
  const session = await stripe.checkout.sessions.retrieve(sessionId, { expand: ["customer"] });
755
969
  if (session && session.payment_status !== "unpaid") {
@@ -777,15 +991,18 @@ export async function authenticateUser({ email, token, sessionId, otpCode, chall
777
991
  if (custStatus !== "active" || isEntitlementRevoked({ email: custEmail, customerId, plan })) {
778
992
  return { ok: false, authenticated: false, error: "This license entitlement has been revoked or refunded." };
779
993
  }
780
- const newToken = signLicenseToken({ email: custEmail, plan, domain, customerId });
994
+ const newToken = signLicenseToken({ email: custEmail, plan, domain, customerId, emailVerified: false });
781
995
  return {
782
996
  ok: true,
783
997
  authenticated: true,
784
998
  email: custEmail,
999
+ emailVerified: false,
1000
+ domainVerified: false,
785
1001
  customerId,
786
1002
  plan,
787
1003
  domain,
788
1004
  inbox: getBotInboxForDomain(domain, custEmail),
1005
+ verificationToken: getDomainVerificationToken(domain, custEmail),
789
1006
  isFounder: false,
790
1007
  token: newToken
791
1008
  };