Lovable published the app. Stripe test mode took 4242. The booking row flipped to paid. The customer never got the confirmation text.
Checkout was not the break. The break was the second system. RapidDev's Lovable Stripe guides tell you to call Resend from the webhook after payment_intent.succeeded, wrap the send in try/catch, and still return 200 so Stripe stops retrying. That is correct webhook hygiene. It also means a failed or never-sent message cannot fail the payment test you just ran.
Short answer
Do not verify Stripe and Resend (or Twilio) as two "connected" badges. Verify the product sentence: one paid charge produces one confirmation the customer can actually receive.
In Cursor, put Lovable's MCP server and FetchSandbox MCP in the same mcp.json. Lovable MCP edits and deploys the project. FetchSandbox MCP runs the payment workflow, the message workflow, and the cross-service charge_then_notify invariant before you hit Publish.
FetchSandbox MCP does not run inside Lovable chat. It runs in Cursor, Claude Code, or another MCP client next to the Lovable server.
Why do isolated connector tests miss the confirmation?
Lovable's Stripe docs stop at deploy, test keys, and 4242. Resend's connector docs tell you to prompt an order-confirmation email and check Resend's activity log with an address you control. Each check can pass while the join is empty:
- the webhook never called Resend
- Resend returned
200and later bounced - Twilio accepted the SMS (
201, statusqueued) and it never reacheddelivered - the email send threw, the
catchlogged it, and Stripe still got200
A reviewer who did not write the Edge Function cannot see that join in the diff.
What does FetchSandbox MCP actually check?
Two single-service runs, then one join.
stripe accept_payment walks customer → PaymentIntent → confirm → capture. On 2026-09-18 that run captured pi_OLPE0TW0IIDP3U77FZ90ZS96.
resend send_email is not done at POST /emails 200. The curated workflow also requires email.sent and email.delivered. Under email_bounced, email be3e58b2-e898-4058-ba04-15f0cc0bd79b was accepted, then the bounce path mattered: a buggy reference handler kept sending to [email protected]; the fixed one suppressed it with 403.
twilio send_sms creates a message and reads it back. Message SMC0LURZLZ2OEA0FVV7IJBG71G5NDYK7IU is the ID to hold onto. queued is not delivered.
The join is charge_then_notify, called as:
quickrun spec_slug=_workflows workflow_name=charge_then_notify
That workflow creates a Stripe customer, creates a PaymentIntent, then posts a Twilio confirmation. The proof is not "every request returned 2xx." The shipped invariants are:
delta(stripe:payment_intents) == delta(twilio:messages)delta(twilio:messages) == 1
On 2026-09-18 both passed (proven: true) for customer cus_F87CHP3YT3SVAS9U, PaymentIntent pi_F9TBJ34V3L5JIWWKQRO307DG, and SMS SMEZIJZQINAJPB7OQTL8JHVL3NGECB8JIM.
Be precise about what that run did not prove. The PaymentIntent came back requires_payment_method. The SMS came back queued. The invariant is the count join, not "the card captured and the phone rang." Keep accept_payment and a message-status read alongside it.
How do you wire both MCP servers in Cursor?
Add both entries inside one mcpServers object. Do not paste a second root key.
{
"mcpServers": {
"lovable": {
"type": "http",
"url": "https://mcp.lovable.dev",
"auth": {
"CLIENT_ID": "6d465f583e1e4ce5801b1616f735670c"
}
},
"fetchsandbox": {
"command": "npx",
"args": ["-y", "fetchsandbox-mcp@latest"]
}
}
}
Reload Cursor. Enable both servers. Ask the agent:
Use Lovable MCP only to inspect the Stripe webhook and the Resend or Twilio send.
Use FetchSandbox MCP to run charge_then_notify, then resend send_email under
email_bounced. I need one payment to produce one confirmation, and I need
email.delivered (or a bounce handled), not POST /emails 200. Paste the receipt.
If the agent already patched the handler, prove_fix cannot use that tree. Keep the still-broken copy, pass the diff, and require the probe to fail on the old code.
Setup detail lives on the install page. The broader AI-builder landing is integration testing for Lovable and Bolt apps.
Receipts
Cross-service charge_then_notify (2026-09-18, flow_run_id run_d3523c12-233b-48ed-b7cc-672b4cecc54d):
POST /v1/customers→ 200 ·cus_F87CHP3YT3SVAS9UPOST /v1/payment_intents→ 200 ·pi_F9TBJ34V3L5JIWWKQRO307DG· statusrequires_payment_methodPOST /2010-04-01/Accounts/AC_demo/Messages.json→ 201 ·SMEZIJZQINAJPB7OQTL8JHVL3NGECB8JIM· statusqueued- invariant: 1 payment intent delta, 1 message delta ·
proven: true
This MCP route does not yet return a /runs/... share URL. The IDs above are from the live quickrun response.
Single-service timelines from the same session:
- Stripe capture path: runs/c426884f16 ·
pi_OLPE0TW0IIDP3U77FZ90ZS96 - Resend bounce: runs/2ee6b5b2ea ·
be3e58b2-e898-4058-ba04-15f0cc0bd79b - Twilio send + fetch: runs/8597792b55 ·
SMC0LURZLZ2OEA0FVV7IJBG71G5NDYK7IU
Reference-handler diffs on those receipts are not a fix proof for your Lovable repo. prove_fix still needs the broken tree.
Questions about verifying Lovable payments and messages
Can I run FetchSandbox MCP inside the Lovable editor?
No. Lovable MCP lets Cursor talk to Lovable. FetchSandbox MCP lets the same agent run Stripe, Resend, and Twilio workflows. Both belong in Cursor. Neither replaces the other.
Is a green Stripe webhook enough if Resend is in try/catch?
No. A swallowed send keeps Stripe happy and the customer uninformed. Assert the message resource, not the webhook status.
Why isn't POST /emails 200 enough?
Resend accepted the payload. email.sent is not delivered. A bounce can follow. Mark confirmation only on email.delivered, or record email.bounced as a failed confirmation.
Does charge_then_notify prove the card captured?
Not in the run above. It proved the count join. Pair it with accept_payment when you need confirm and capture.
What about a retry that sends two texts?
charge_then_notify has no idempotency key today. Repeating it is supposed to create another charge and another SMS (identical_deltas). Your production handler still needs Stripe's Idempotency-Key and a unique constraint on the message side. That is a separate prove_fix claim.