> ## Documentation Index
> Fetch the complete documentation index at: https://paperplane-justin-winter-s-projects.vercel.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed the send flow

> Let visitors mail documents from your own site — one script, two widgets, live demos you can click.

Drop one script on your page and any element becomes a "mail this" trigger.
Two widgets, same modal pattern, no account or API key:

* **`paperplane.js`** — the full send flow (compose or upload, address, pay).
  For "mail this document" on tools and dashboards.
* **`template-embed.js`** — a single pre-filled letter template (deposit
  demand, cease & desist, late-rent notice, 50+ more). For content pages where
  the reader's next step is one specific letter.

```html Full send flow theme={null}
<script defer src="https://sendpaperplane.com/paperplane.js"></script>
<button data-paperplane>Mail this document</button>
```

```html One specific template theme={null}
<script defer src="https://sendpaperplane.com/template-embed.js"></script>
<button data-paperplane-template="security-deposit-demand-letter">
  Start my demand letter
</button>
```

Buttons carry their own template key, so one page can offer several letters.
Every key in the [template gallery](https://sendpaperplane.com/templates) works.

## See it live, in context

Three fictional host sites with the real widget wired in — **click the buttons,
they work** (the flow is the live guest checkout; nothing is mailed unless
someone pays):

### A news / advocacy article

Inline reader tool next to the story it serves — the highest-converting spot,
because intent peaks mid-article.

<iframe src="https://sendpaperplane.com/embed-demos/news" width="100%" height="560" style={{ border: "1px solid #e5e5e5", borderRadius: "12px" }} title="News site embed demo" allow="geolocation" />

### A legal-aid letter library

Multiple templates, one script — each button carries its own
`data-paperplane-template` key.

<iframe src="https://sendpaperplane.com/embed-demos/legal" width="100%" height="560" style={{ border: "1px solid #e5e5e5", borderRadius: "12px" }} title="Legal-aid site embed demo" allow="geolocation" />

### A small-business portal

Both widgets together: the generic send flow for arbitrary documents plus a
pre-filled compliance template.

<iframe src="https://sendpaperplane.com/embed-demos/smb" width="100%" height="560" style={{ border: "1px solid #e5e5e5", borderRadius: "12px" }} title="Small business embed demo" allow="geolocation" />

## How it works / security

* Each script is \~3kb, dependency-free; it renders a modal iframe of the
  no-chrome flow (`/embed` or `/templates/embed/{key}`), with its own 44px
  in-frame header (paperplane wordmark, "Printed & mailed by paperplane ·
  USPS", a close control) and a slim footer with Terms/Privacy links.
* The app allows third-party framing **only** on those embed routes
  (`Content-Security-Policy: frame-ancestors *` scoped per-route) — nothing
  else on the app can be framed, so nothing else can be clickjacked.
* **Payment happens inside the frame.** Card payment uses [Stripe Embedded
  Checkout](https://stripe.com/docs/checkout/embedded/quickstart) — Stripe's
  hosted Checkout page refuses to load inside any iframe at all, so
  `paperplane.js`'s iframe needs `allow="payment"` (already set) for the
  in-frame card fields and wallet buttons to work. Apple Pay only appears for
  domains registered with Stripe — paperplane's own domain is, an arbitrary
  third-party host is not, and no `allow` attribute changes that; Google Pay,
  Link, and card entry are unaffected.
* **Closing is never destructive.** Backdrop click, the header's × control,
  and Escape all go through the same confirmation: closing with a draft in
  progress shows "Discard this letter?" first, closing with nothing to lose
  (an untouched step 1) closes immediately, and none of them can close while
  a submission or payment is in flight. The modal is *hidden*
  (`display:none`), not unloaded — reopening within the same page load
  restores the draft instead of reloading `/embed` from scratch.
* **Prefill.** A trigger button's `data-template`, `data-address`, and
  `data-class` attributes are forwarded onto `/embed`'s query string when it
  opens — same `?template=`/`?address=`/`?class=` the standalone `/send` page
  reads. All three are starting points a visitor can still change, not
  trusted input.
* **Events.** `window.paperplane.on('opened' | 'closed' | 'ordered', fn)` —
  `'ordered'` fires once per completed order with
  `{ orderId, amountCents, mailClass }`. The same three also dispatch as
  `document`-level CustomEvents (`paperplane:opened` etc.) if you'd rather
  `addEventListener`.
* Template embeds show "Powered by *your site* & paperplane" from the referrer
  (best-effort; plain paperplane branding when the referrer is blocked).
* Visitors stay guests: address verification, transparent pricing, payment,
  and a private tracking link — all inside the modal. Because third-party
  iframe storage is partitioned per top-level site, the full send flow
  requires an email in the embed context (it's the only way to find the order
  again later), and "Track your letter" opens in the top-level page rather
  than the iframe.
* **Co-branding.** Both widgets are subtly co-branded: a small "Powered by
  paperplane" line (with the mark) sits at the bottom of the full send flow,
  and the template widget's "Powered by *your site* & paperplane" line links
  back to paperplane the same way. Neither is optional on the default plan.
  A 100% whitelabel option — the paperplane mention removed entirely — is
  available; [contact sales](https://sendpaperplane.com/contact) to turn it
  on for your embed.
* The address field's "Use my location" button needs the iframe to grant
  geolocation — `paperplane.js` and `template-embed.js` already set
  `allow="geolocation; payment"` (`template-embed.js` omits `payment`, since
  it never reaches a card) on the iframe they create. If you build your own
  iframe wrapper around `/embed` or `/templates/embed/{key}` instead of using
  the provided script, add the same `allow` values yourself or those features
  will silently fail (the browser blocks geolocation and payment in a
  cross-origin iframe by default).

## Where hosts use it

1. **Publishers & advocacy** — the reader tool beside the article (demo above).
2. **Legal & professional services** — self-help letter libraries; certified
   mail is the standard courts expect.
3. **SMB portals** — property management, billing, back-office "mail this
   notice" buttons that replace the post-office run.

## Limits

The embedded flow inherits the same guest limits as the web surface (see
[Rate limits](/docs/guides/rate-limits)); no separate quota, no host-site keys to
rotate.
