diff --git a/docs/strategy/market-parity-benchmark-2026.md b/docs/strategy/market-parity-benchmark-2026.md index abdb9f3d..be8695fb 100644 --- a/docs/strategy/market-parity-benchmark-2026.md +++ b/docs/strategy/market-parity-benchmark-2026.md @@ -56,7 +56,7 @@ Current public market signals: | Automated workflow depth | can claim | Automation prepares intake, research, drafts, QA, evidence, handoff, and routing; humans decide fit, claims, delivery, acceptance, and renewal. | | Tangible improvement cadence | do not claim yet | 0 canonical paid client folder(s); 0 client-ready folder(s). | | Market traction | do not claim yet | 0/5 Looms, 0/5 sends, 0 replies. | -| Sender trust | do not claim yet | missing physical postal address; DKIM selector not configured | +| Sender trust | do not claim yet | missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured | | Proof-run discipline | do not claim yet | proof run needs-recording; approved rows 0; sent rows 0. | ## TinyStudio Pricing Interpretation diff --git a/growth-brain/ops/11-10-proof-run.md b/growth-brain/ops/11-10-proof-run.md index 9fa8707a..96dae5dc 100644 --- a/growth-brain/ops/11-10-proof-run.md +++ b/growth-brain/ops/11-10-proof-run.md @@ -12,7 +12,7 @@ Parity score: 5/10. | Area | Current Evidence | Required Proof | |---|---|---| -| Sender trust | missing physical postal address; DKIM selector not configured | Run `npm run send:configure -- --physical-address="..." --dkim-selector=... --dry-run` with the real values, then apply it without `--dry-run`. Until then, use contact forms or DMs. | +| Sender trust | missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured | Run `npm run send:configure -- --physical-address="..." --dkim-selector=... --dry-run` with the real values, then apply it without `--dry-run`. Until then, use contact forms or DMs. | | Market proof | 0/5 Looms, 0/5 sends, 0 replies | Record and send 5 approved Looms with fault, impact, fix, and ask notes. | | Sales proof | 0 external client(s) with a validated application, human fit approval, and paid Day 0 | Capture at least 1 external consented application, human fit approval, and validated paid Day 0 record. | | Delivery proof | 0 external paid client(s) ready with approved delivery; 0 with approved claims | Complete hash-bound human approval, implementation acceptance, approved claims, scorecard, and client readiness for that paid client. | diff --git a/growth-brain/ops/competitive-proof-matrix.html b/growth-brain/ops/competitive-proof-matrix.html index f9a4ec00..2f50ebe1 100644 --- a/growth-brain/ops/competitive-proof-matrix.html +++ b/growth-brain/ops/competitive-proof-matrix.html @@ -129,7 +129,7 @@

TinyStudio Proof Bar

Sender trust do not claim yet - missing physical postal address; DKIM selector not configured + missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured Proof-run discipline diff --git a/growth-brain/ops/competitive-proof-matrix.md b/growth-brain/ops/competitive-proof-matrix.md index abdb9f3d..be8695fb 100644 --- a/growth-brain/ops/competitive-proof-matrix.md +++ b/growth-brain/ops/competitive-proof-matrix.md @@ -56,7 +56,7 @@ Current public market signals: | Automated workflow depth | can claim | Automation prepares intake, research, drafts, QA, evidence, handoff, and routing; humans decide fit, claims, delivery, acceptance, and renewal. | | Tangible improvement cadence | do not claim yet | 0 canonical paid client folder(s); 0 client-ready folder(s). | | Market traction | do not claim yet | 0/5 Looms, 0/5 sends, 0 replies. | -| Sender trust | do not claim yet | missing physical postal address; DKIM selector not configured | +| Sender trust | do not claim yet | missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured | | Proof-run discipline | do not claim yet | proof run needs-recording; approved rows 0; sent rows 0. | ## TinyStudio Pricing Interpretation diff --git a/growth-brain/ops/market-parity-readiness.md b/growth-brain/ops/market-parity-readiness.md index 0272b3e5..c83c9b57 100644 --- a/growth-brain/ops/market-parity-readiness.md +++ b/growth-brain/ops/market-parity-readiness.md @@ -25,7 +25,7 @@ Not 11/10 yet: current evidence proves the internal system, but sender trust, ma | Automation coverage | pass | recording, send, follow-up, sales, delivery, proof, and metrics surfaces are generated | | Stress-tested internals | pass | kit, claim, and send gates pass on current repo state | | Comparable price/value | pass | The Website Correction; $1,000 founder pilot; scope one highest-leverage page | -| Sender trust | fail | missing physical postal address; DKIM selector not configured | +| Sender trust | fail | missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured | | Market proof | fail | 0/5 Looms, 0/5 sends, 0 replies | | Sales proof | fail | 0 external client(s) with a validated application, human fit approval, and paid Day 0 | | Delivery proof | fail | 0 external paid client(s) ready with approved delivery; 0 with approved claims | diff --git a/growth-brain/ops/sender-setup-guide.html b/growth-brain/ops/sender-setup-guide.html index f003bfaa..f41aa591 100644 --- a/growth-brain/ops/sender-setup-guide.html +++ b/growth-brain/ops/sender-setup-guide.html @@ -94,6 +94,13 @@

DNS Checks

+
+ Outbound mail path + needs work + route3.mx.cloudflare.net, route1.mx.cloudflare.net, route2.mx.cloudflare.net +
+ +
DKIM discovery needs work @@ -107,6 +114,9 @@

Fix First

  • missing physical postal address
    Commercial email needs a valid physical postal address. Use a business address, PO box, or private mailbox before cold email.
  • +
  • outbound mail path is inbound-only
    MX records point at Cloudflare Email Routing (route3.mx.cloudflare.net, route1.mx.cloudflare.net, route2.mx.cloudflare.net), which forwards inbound mail only. Connect a sending provider (Google Workspace, Zoho Mail, Outlook, Resend, Postmark, or SendGrid), enable DKIM there, then save its exact selector as dkimSelector.
  • + +
  • DKIM selector not configured
    Set dkimSelector after enabling DKIM in the mail provider. No common DKIM selectors were found in DNS.
  • @@ -118,10 +128,11 @@

    DKIM Discovery

    Order

      -
    1. Add the sender postal address in agency-config.json.
    2. -
    3. Enable DKIM in the mail provider and copy the selector.
    4. +
    5. Connect an outbound sending provider (Cloudflare Email Routing forwards inbound mail only and cannot send).
    6. +
    7. Add the sender postal address in agency-config.json (business address, PO box, or private mailbox).
    8. +
    9. Enable DKIM in the outbound provider and copy the selector.
    10. +
    11. Add the DKIM TXT record in Cloudflare DNS at <selector>._domainkey.tinystudio.io.
    12. Save the selector as dkimSelector.
    13. -
    14. Add the DKIM TXT record in Cloudflare if the provider gives one.
    15. Run npm run send:setup.
    diff --git a/growth-brain/ops/sender-setup-guide.md b/growth-brain/ops/sender-setup-guide.md index 9fe56abd..5685ad2c 100644 --- a/growth-brain/ops/sender-setup-guide.md +++ b/growth-brain/ops/sender-setup-guide.md @@ -17,6 +17,7 @@ Use this before cold email. Until this guide is clean, use contact forms or DMs |---|---|---| | SPF | ready | tinystudio.io | | DMARC | ready | _dmarc.tinystudio.io | +| Outbound mail path | needs work | route3.mx.cloudflare.net, route1.mx.cloudflare.net, route2.mx.cloudflare.net | | DKIM discovery | needs work | common selectors at _domainkey.tinystudio.io | ## Warnings To Fix @@ -24,6 +25,7 @@ Use this before cold email. Until this guide is clean, use contact forms or DMs | Warning | What It Means | |---|---| | missing physical postal address | Commercial email needs a valid physical postal address. Use a business address, PO box, or private mailbox before cold email. | +| outbound mail path is inbound-only | MX records point at Cloudflare Email Routing (route3.mx.cloudflare.net, route1.mx.cloudflare.net, route2.mx.cloudflare.net), which forwards inbound mail only. Connect a sending provider (Google Workspace, Zoho Mail, Outlook, Resend, Postmark, or SendGrid), enable DKIM there, then save its exact selector as dkimSelector. | | DKIM selector not configured | Set dkimSelector after enabling DKIM in the mail provider. No common DKIM selectors were found in DNS. | ## DKIM Discovery @@ -40,12 +42,13 @@ npm run send:configure -- --physical-address="..." --dkim-selector=... --dry-run ## Fix Order -1. Add a real sender postal address to `senderPhysicalAddress` in `growth-brain/ops/agency-config.json`. -2. In the mail provider for `tinystudio.io`, enable DKIM and copy the selector. -3. Add the selector to `dkimSelector` in `growth-brain/ops/agency-config.json`. -4. If the mail provider gives a DKIM TXT record, add it in Cloudflare DNS at `._domainkey.tinystudio.io`. -5. Run `npm run send:setup`. -6. If it is clean, email can join contact forms and DMs as an outbound route. +1. Connect an outbound sending provider for `tinystudio.io` (Cloudflare Email Routing forwards inbound mail only and cannot send). +2. Add a real sender postal address to `senderPhysicalAddress` in `growth-brain/ops/agency-config.json` (business address, PO box, or private mailbox). +3. In the outbound provider, enable DKIM and copy the selector. +4. Add the provider's DKIM TXT record in Cloudflare DNS at `._domainkey.tinystudio.io`. +5. Save the selector as `dkimSelector` in `growth-brain/ops/agency-config.json`. +6. Run `npm run send:setup`. +7. If it is clean, email can join contact forms and DMs as an outbound route. ## Notes diff --git a/scripts/check-outbound-sender-setup.mjs b/scripts/check-outbound-sender-setup.mjs index 41911f35..34dc05dd 100644 --- a/scripts/check-outbound-sender-setup.mjs +++ b/scripts/check-outbound-sender-setup.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { resolveTxt } from "node:dns/promises"; +import { resolveMx, resolveTxt } from "node:dns/promises"; import { agencyConfig } from "./lib/agency-config.mjs"; const strict = process.argv.includes("--strict"); @@ -47,6 +47,17 @@ async function txtRecords(domain) { } } +async function mxRecords(domain) { + try { + const records = await resolveMx(domain); + return records + .sort((a, b) => a.priority - b.priority) + .map((record) => record.exchange.toLowerCase()); + } catch { + return []; + } +} + function warn(rule, detail) { warnings.push({ rule, detail }); } @@ -91,6 +102,21 @@ if (!domain) { checks.push({ name: "DMARC", status: dmarc ? "found" : "missing", domain: dmarcDomain }); if (!dmarc) warn("missing DMARC", `No DMARC TXT record found for ${dmarcDomain}.`); + const mx = await mxRecords(domain); + if (mx.length) { + const inboundOnly = mx.every((host) => /^route\d+\.mx\.cloudflare\.net\.?$/.test(host)); + checks.push({ name: "Outbound mail path", status: inboundOnly ? "missing" : "found", domain: mx.join(", ") }); + if (inboundOnly && !config.dkimSelector) { + warn( + "outbound mail path is inbound-only", + `MX records point at Cloudflare Email Routing (${mx.join(", ")}), which forwards inbound mail only. Connect a sending provider (Google Workspace, Zoho Mail, Outlook, Resend, Postmark, or SendGrid), enable DKIM there, then save its exact selector as dkimSelector.` + ); + } + } else { + checks.push({ name: "Outbound mail path", status: "missing", domain: `no MX records for ${domain}` }); + warn("no MX records for sender domain", "No MX records exist, so replies to the sender address would bounce. Add MX records or use a sender domain that can receive replies before cold email."); + } + if (config.dkimSelector) { const dkimDomain = `${config.dkimSelector}._domainkey.${domain}`; const dkimTxt = await txtRecords(dkimDomain); diff --git a/scripts/export-sender-setup-guide.mjs b/scripts/export-sender-setup-guide.mjs index 1b64937a..329a2c76 100644 --- a/scripts/export-sender-setup-guide.mjs +++ b/scripts/export-sender-setup-guide.mjs @@ -98,12 +98,13 @@ ${configureCommand} ## Fix Order -1. Add a real sender postal address to \`senderPhysicalAddress\` in \`growth-brain/ops/agency-config.json\`. -2. In the mail provider for \`${setup.senderDomain || "the sender domain"}\`, enable DKIM and copy the selector. -3. Add the selector to \`dkimSelector\` in \`growth-brain/ops/agency-config.json\`. -4. If the mail provider gives a DKIM TXT record, add it in Cloudflare DNS at \`${dkimHost}\`. -5. Run \`npm run send:setup\`. -6. If it is clean, email can join contact forms and DMs as an outbound route. +1. Connect an outbound sending provider for \`${setup.senderDomain || "the sender domain"}\` (Cloudflare Email Routing forwards inbound mail only and cannot send). +2. Add a real sender postal address to \`senderPhysicalAddress\` in \`growth-brain/ops/agency-config.json\` (business address, PO box, or private mailbox). +3. In the outbound provider, enable DKIM and copy the selector. +4. Add the provider's DKIM TXT record in Cloudflare DNS at \`${dkimHost}\`. +5. Save the selector as \`dkimSelector\` in \`growth-brain/ops/agency-config.json\`. +6. Run \`npm run send:setup\`. +7. If it is clean, email can join contact forms and DMs as an outbound route. ## Notes @@ -235,10 +236,11 @@ const html = `

    Order

      -
    1. Add the sender postal address in agency-config.json.
    2. -
    3. Enable DKIM in the mail provider and copy the selector.
    4. +
    5. Connect an outbound sending provider (Cloudflare Email Routing forwards inbound mail only and cannot send).
    6. +
    7. Add the sender postal address in agency-config.json (business address, PO box, or private mailbox).
    8. +
    9. Enable DKIM in the outbound provider and copy the selector.
    10. +
    11. Add the DKIM TXT record in Cloudflare DNS at ${escapeHtml(dkimHost)}.
    12. Save the selector as dkimSelector.
    13. -
    14. Add the DKIM TXT record in Cloudflare if the provider gives one.
    15. Run npm run send:setup.