Replit Agent just wired Paddle checkout and Resend confirmations. Secrets look filled. You are one Publish away from a .replit.app URL that can take money and send mail.
Paddle's own docs still send you to a tunnel and the webhook simulator. Resend's docs still send you to a dashboard endpoint or resend webhooks listen. Neither one is a dry-run of the action your Repl is about to fire.
This walkthrough adds FetchSandbox as a Replit MCP connector, boots Paddle and Resend twins (API-compatible hosts, not a tap on live traffic), and checks payment_failed and email_bounced before live keys.
Short answer
- Create a key at fetchsandbox.com/keys.
- On replit.com/integrations, + Add MCP server →
https://fetchsandbox.com/mcp/v1→ Advanced headerAuthorization: Bearer <key>. Paste the token. Do not put${SECRET}in that field — Replit sends the header exactly as written. Test & save. - Ask Agent to boot a Paddle twin and a Resend twin, then put the twin
sandbox_base_urlvalues in Replit Secrets and point the Paddle/Resend clients at those hosts. quickrunPaddlesubscription_lifecycle, then armpayment_failed.quickrunResendsend_email, then armemail_bounced. Check the membership (or order) row and the email.delivered vs email.bounced events — not a 200.
Official Paddle and Resend testing still stop at a signed simulator POST and a green webhook 200. That is the happy path.
Step 1: Build the Repl Agent can actually publish
In the Project Editor, prompt Agent for its stack (the Repl's language, not a Lovable Cloud app and not a Next.js rewrite unless that is already the Repl):
Build a paid workshop checkout: Paddle for the transaction,
Resend for the confirmation email. Handle a declined payment
and a bounced confirmation without unlocking access. We will
point Paddle and Resend at FetchSandbox twins, not live APIs.
You need a checkout path and a place the UI reads for "paid" or "enrolled." A stubbed button that always says success is not enough.
Do not paste FetchSandbox keys into Agent chat.
Step 2: Add FetchSandbox as a Replit connector
Replit Agent talks to MCP servers from Integrations, not from a file inside one Repl. The connection is account-level.
- Open replit.com/integrations (or the Integrations pane in the Project Editor).
- Scroll to MCP Servers for Replit Agent.
- + Add MCP server.
| Field | Value |
|---|---|
| Display name | FetchSandbox |
| MCP server URL | https://fetchsandbox.com/mcp/v1 |
| Auth | Advanced → custom header |
| Header name | Authorization |
| Header value | Bearer fsk_… from fetchsandbox.com/keys |
Test & save. Replit will probe the server. This endpoint is streamable HTTP with a Bearer key. It is not OAuth, and it is not the Stripe/Linear one-click catalog.
If Test & save 401s, the header is missing, still says Bearer ${FETCHSANDBOX_KEY}, or used None. Mint a new key at /keys — the list only shows prefixes after the first copy.
Replit's scanner inspects tool definitions before Agent can call them. That is their control. It does not run Paddle or Resend for you.
Same MCP URL Cursor and Claude use: FetchSandbox MCP.
Step 3: Boot twins and point the Repl at them
In Agent chat:
Use FetchSandbox MCP. Boot a Paddle twin and a Resend twin.
Give me sandbox_base_url and sandbox_api_key for each.
Put those in Replit Secrets. Point the Paddle and Resend
clients at the twin hosts, not api.paddle.com or api.resend.com.
FetchSandbox does not sit in front of live Paddle and capture production traffic. The Repl must call the twin host. If Secrets still hold https://api.paddle.com, the twin never sees the request.
Then a sanity run — MCP quickrun, not a tool named validate:
quickrun the Paddle subscription_lifecycle workflow.
Show the subscription id and status, then show the columns
the checkout UI uses to decide this user is enrolled.
If Paddle says paid and the gate still says visitor, stop. Same class of bug as paid without unlock, on a different builder.
Resend:
quickrun Resend send_email after a successful checkout.
Show POST /emails status and whether email.sent and
email.delivered exist on the twin. Do not claim delivered
from a 200 alone.
POST /emails 200 means the twin accepted the send. Delivery is a webhook. The send_email workflow requires email.sent and email.delivered, and forbids email.bounced.
Step 4: Arm the failures Replit Publish will actually hit
Ask the sandbox what it can inject, then arm named scenarios. Do not invent Paddle webhook_retries — that name is a Stripe scenario. Paddle's list is whatever that twin prints.
What failure scenarios can this Paddle sandbox inject?
Arm payment_failed and run checkout again.
Then arm rate_limited and run it again.
On the Resend twin, arm email_bounced and send the
confirmation again. Did the UI still say enrolled / emailed?
| What you are afraid of | Twin scenario | What you check |
|---|---|---|
| Payment declined, UI still unlocks | Paddle payment_failed | No enrolled/member row |
| Paddle 429 mid-checkout | Paddle rate_limited | App waits or surfaces an error; it does not drop the join with a 200 UI |
| Bad or missing Paddle key | Paddle auth_failure | 401, not a fake success |
| Confirmation never arrived | Resend email_bounced | You do not treat bounce as delivered; you do not unlock on send-200 |
| Resend key wrong | Resend auth_failure | No silent "email sent" |
email_bounced is the Resend failure with a real evidence bar in our corpus. The twin accepts the send, then reports it was never delivered. If Agent only checked POST /emails 200, Publish will lie.
Step 5: Fix, then run the same failure again
payment_failed still enrolled the user. Do not upsert
access until the transaction is paid. Arm payment_failed
again and show the gate row.
email_bounced still showed confirmation sent. Do not
flip emailed until email.delivered exists, and suppress
the address on bounce. Arm email_bounced again.
The second run is the proof. "I asked Agent to fix it" is not. Attach the receipt URL (requests in order, twin state, webhook events, verdict) to the Repl's README or the PR.
Same hold, different surface: dry-run agent actions before production.
What this is not
- Not live Paddle or Resend. Swap real keys after the twin join and the bounce path hold.
- Not Gmail. Twin
POST /emails200 ≠ inbox. - Not Paddle's webhook simulator or a tunnel to the Repl. Those prove your route returns 2xx. They do not prove Agent's action against a stateful twin.
- Not an intercepting proxy. If the app still talks to
api.paddle.com, the connector is a spectator. - Not a Firecracker/code sandbox. Replit already isolates the Repl. This dry-runs the outbound API action.
Broader AI-builder loop (Lovable, Bolt, v0, Replit): integration testing for AI-built apps. Paddle twin docs: /docs/paddle. Resend twin: /docs/resend.
Questions about this Replit walkthrough
Do I need FetchSandbox to build the Replit UI?
No. Replit Agent builds the UI. FetchSandbox is how you prove Paddle and Resend behavior before live keys, through the same MCP connector Agent already uses for other tools.
Can I skip the Authorization header on the Replit MCP form?
No. Use a Bearer token from fetchsandbox.com/keys. A custom server with no header 401s. Do not paste ${ENV} into the header value — Replit does not expand it.
Why Paddle and Resend instead of Stripe?
This Repl is billed and emailed with Paddle and Resend. Stripe twins are a different spec (accept_payment, payment_declined, webhook_retries). Ask each sandbox what it can inject; do not copy scenario names across providers.
Is a webhook 200 enough?
No. 200 means your route returned 200. Enrolled means the access columns changed once. Emailed means email.delivered fired, not that email.bounced did.