Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/strategy/market-parity-benchmark-2026.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,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; outbound mail path is inbound-only; DKIM selector not configured |
| Sender trust | do not claim yet | missing physical postal address |
| Proof-run discipline | do not claim yet | proof run needs-recording; approved rows 0; sent rows 0. |

## TinyStudio Pricing Interpretation
Expand Down
4 changes: 2 additions & 2 deletions growth-brain/ops/11-10-proof-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Parity score: 5/10.

| Area | Current Evidence | Required Proof |
|---|---|---|
| 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. |
| Sender trust | missing physical postal address | 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. |
Expand Down Expand Up @@ -44,7 +44,7 @@ npm run market:proof-check

5. Send from the outbox, using the route shown for each prospect. Recommended channel right now: contact form or DM.

Sender warnings: missing physical postal address; DKIM selector not configured.
Sender warnings: missing physical postal address.

6. After sending, use the outbox copied sent sheet:

Expand Down
2 changes: 1 addition & 1 deletion growth-brain/ops/agency-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"optOutLine": "If this is not useful, reply no and I will not follow up.",
"senderEmail": "hello@tinystudio.io",
"senderPhysicalAddress": "",
"dkimSelector": "",
"dkimSelector": "resend",
"manualDailySendCap": 20,
"humanDailyReviewCap": 20,
"meetingPlaceholder": "add meeting link",
Expand Down
2 changes: 1 addition & 1 deletion growth-brain/ops/competitive-proof-matrix.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ <h2>TinyStudio Proof Bar</h2>
<tr>
<td>Sender trust</td>
<td><span class="pill bad">do not claim yet</span></td>
<td>missing physical postal address; outbound mail path is inbound-only; DKIM selector not configured</td>
<td>missing physical postal address</td>
</tr>
<tr>
<td>Proof-run discipline</td>
Expand Down
2 changes: 1 addition & 1 deletion growth-brain/ops/competitive-proof-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,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; outbound mail path is inbound-only; DKIM selector not configured |
| Sender trust | do not claim yet | missing physical postal address |
| Proof-run discipline | do not claim yet | proof run needs-recording; approved rows 0; sent rows 0. |

## TinyStudio Pricing Interpretation
Expand Down
2 changes: 1 addition & 1 deletion growth-brain/ops/market-parity-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -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; outbound mail path is inbound-only; DKIM selector not configured |
| Sender trust | fail | missing physical postal address |
| 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 |
Expand Down
16 changes: 5 additions & 11 deletions growth-brain/ops/sender-setup-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,36 +102,30 @@ <h2>DNS Checks</h2>


<div class="card">
<span>DKIM discovery</span>
<strong>needs work</strong>
<small>common selectors at _domainkey.tinystudio.io</small>
<span>DKIM</span>
<strong>ready</strong>
<small>resend._domainkey.tinystudio.io</small>
</div>
</div>
</section>
<section>
<h2>Fix First</h2>
<ol>
<li><strong>missing physical postal address</strong><br>Commercial email needs a valid physical postal address. Use a business address, PO box, or private mailbox before cold email.</li>


<li><strong>outbound mail path is inbound-only</strong><br>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.</li>


<li><strong>DKIM selector not configured</strong><br>Set dkimSelector after enabling DKIM in the mail provider. No common DKIM selectors were found in DNS.</li>
</ol>
</section>
<section>
<h2>DKIM Discovery</h2>
<div class="grid"><p>No common DKIM selector found in DNS yet.</p></div>
<p><code>npm run send:configure -- --physical-address=&quot;...&quot; --dkim-selector=... --dry-run</code></p>
<p><code>npm run send:configure -- --physical-address=&quot;...&quot; --dkim-selector=resend --dry-run</code></p>
</section>
<section>
<h2>Order</h2>
<ol>
<li>Connect an outbound sending provider (Cloudflare Email Routing forwards inbound mail only and cannot send).</li>
<li>Add the sender postal address in <code>agency-config.json</code> (business address, PO box, or private mailbox).</li>
<li>Enable DKIM in the outbound provider and copy the selector.</li>
<li>Add the DKIM TXT record in Cloudflare DNS at <code>&lt;selector&gt;._domainkey.tinystudio.io</code>.</li>
<li>Add the DKIM TXT record in Cloudflare DNS at <code>resend._domainkey.tinystudio.io</code>.</li>
<li>Save the selector as <code>dkimSelector</code>.</li>
<li>Run <code>npm run send:setup</code>.</li>
</ol>
Expand Down
18 changes: 7 additions & 11 deletions growth-brain/ops/sender-setup-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,33 @@ 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 |
| DKIM | ready | resend._domainkey.tinystudio.io |

## Warnings To Fix

| 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

| Selector | DNS Host |
|---|---|
| - | No common DKIM selector found in DNS yet. |
| resend | resend._domainkey.tinystudio.io |

Suggested dry-run command:

```bash
npm run send:configure -- --physical-address="..." --dkim-selector=... --dry-run
npm run send:configure -- --physical-address="..." --dkim-selector=resend --dry-run
```

## Fix Order

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 `<selector>._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.
2. Add a real sender postal address to `senderPhysicalAddress` in `growth-brain/ops/agency-config.json` (business address, PO box, or private mailbox). The address must be a published business location, never a placeholder.
3. DKIM is configured for `resend` at `resend._domainkey.tinystudio.io`. Confirm the selector still matches the mail provider if you swap providers.
4. Run `npm run send:setup`.
5. If it is clean, email can join contact forms and DMs as an outbound route.

## Notes

Expand Down
31 changes: 30 additions & 1 deletion scripts/check-outbound-sender-setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,36 @@ const dkimSelectorCandidates = [
"pm",
"mx",
"sig1",
"sig2"
"sig2",
// Resend, Postmark, Mailgun, Amazon SES, Mailjet, Brevo, SparkPost, Klaviyo,
// HubSpot, Mailchimp, Elastic Email, MailerSend, Fastmail, Yandex, ProtonMail
// bridge, Mailgun alt, Mailchimp transactional, and Postmark alt selectors.
// Without these the sender trust check would miss a real selector the
// provider published in DNS and flag it as unconfigured.
"resend",
"postmark",
"mg",
"amazonses",
"ses",
"mailjet",
"brevo",
"sendinblue",
"sparkpost",
"sp",
"klaviyo",
"hubspot",
"hs1",
"hs2",
"mailchimp",
"mc",
"elasticemail",
"ee",
"mailersend",
"ms",
"fastmail",
"fm1",
"fm2",
"tutanota"
];

function senderDomain() {
Expand Down
37 changes: 27 additions & 10 deletions scripts/export-sender-setup-guide.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,33 @@ function dkimCandidateRows(candidates) {
const setup = runJson(["scripts/check-outbound-sender-setup.mjs"]);
const dkimCandidates = setup.dkimCandidates || [];
const firstDkimCandidate = dkimCandidates[0]?.selector || "";
const configureCommand = `npm run send:configure -- --physical-address="..." --dkim-selector=${firstDkimCandidate || "..."} --dry-run`;
const dkimHost = config.dkimSelector && setup.senderDomain
const dkimSelectorConfigured = Boolean(config.dkimSelector);
const dkimProviderStep = dkimSelectorConfigured
? `DKIM is configured for \`${config.dkimSelector}\` at \`${config.dkimSelector}._domainkey.${setup.senderDomain}\`. Confirm the selector still matches the mail provider if you swap providers.`
: `In the outbound provider, enable DKIM and copy the selector.`;
const dkimHost = dkimSelectorConfigured && setup.senderDomain
? `${config.dkimSelector}._domainkey.${setup.senderDomain}`
: (firstDkimCandidate ? `${firstDkimCandidate}._domainkey.${setup.senderDomain}` : "<selector>._domainkey." + (setup.senderDomain || "tinystudio.io"));
const physicalAddressStep = `Add a real sender postal address to \`senderPhysicalAddress\` in \`growth-brain/ops/agency-config.json\` (business address, PO box, or private mailbox). The address must be a published business location, never a placeholder.`;
const providerConnectStep = `Connect an outbound sending provider for \`${setup.senderDomain || "the sender domain"}\` (Cloudflare Email Routing forwards inbound mail only and cannot send).`;
const configureSelectorArg = config.dkimSelector ? `--dkim-selector=${config.dkimSelector}` : `--dkim-selector=${firstDkimCandidate || "..."}`;
const configureCommand = `npm run send:configure -- --physical-address="..." ${configureSelectorArg} --dry-run`;
const fixSteps = [];
fixSteps.push(providerConnectStep);
if (!config.senderPhysicalAddress) fixSteps.push(physicalAddressStep);
if (!dkimSelectorConfigured) {
fixSteps.push(dkimProviderStep);
fixSteps.push(`Add the provider's DKIM TXT record in Cloudflare DNS at \`${dkimHost}\`.`);
fixSteps.push("Save the selector as `dkimSelector` in `growth-brain/ops/agency-config.json`.");
} else {
fixSteps.push(dkimProviderStep);
}
fixSteps.push("Run `npm run send:setup`.");
fixSteps.push("If it is clean, email can join contact forms and DMs as an outbound route.");
const fixOrder = fixSteps.map((step, index) => `${index + 1}. ${step}`).join("\n");
const dkimDiscoveryRow = dkimSelectorConfigured
? `| ${config.dkimSelector} | ${dkimHost} |`
: "| - | No common DKIM selector found in DNS yet. |";

const markdown = `# Sender Setup Guide

Expand Down Expand Up @@ -88,7 +111,7 @@ ${warningRows(setup.warnings || [])}

| Selector | DNS Host |
|---|---|
${dkimCandidateRows(dkimCandidates)}
${dkimDiscoveryRow}

Suggested dry-run command:

Expand All @@ -98,13 +121,7 @@ ${configureCommand}

## Fix Order

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.
${fixOrder}

## Notes

Expand Down