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
- - Add the sender postal address in
agency-config.json.
- - Enable DKIM in the mail provider and copy the selector.
+ - Connect an outbound sending provider (Cloudflare Email Routing forwards inbound mail only and cannot send).
+ - Add the sender postal address in
agency-config.json (business address, PO box, or private mailbox).
+ - Enable DKIM in the outbound provider and copy the selector.
+ - Add the DKIM TXT record in Cloudflare DNS at
<selector>._domainkey.tinystudio.io.
- Save the selector as
dkimSelector.
- - Add the DKIM TXT record in Cloudflare if the provider gives one.
- 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
- - Add the sender postal address in
agency-config.json.
- - Enable DKIM in the mail provider and copy the selector.
+ - Connect an outbound sending provider (Cloudflare Email Routing forwards inbound mail only and cannot send).
+ - Add the sender postal address in
agency-config.json (business address, PO box, or private mailbox).
+ - Enable DKIM in the outbound provider and copy the selector.
+ - Add the DKIM TXT record in Cloudflare DNS at
${escapeHtml(dkimHost)}.
- Save the selector as
dkimSelector.
- - Add the DKIM TXT record in Cloudflare if the provider gives one.
- Run
npm run send:setup.