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.
Full send flow
One specific template
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.A legal-aid letter library
Multiple templates, one script — each button carries its owndata-paperplane-template key.
A small-business portal
Both widgets together: the generic send flow for arbitrary documents plus a pre-filled compliance template.How it works / security
- Each script is ~3kb, dependency-free; it renders a modal iframe of the
no-chrome flow (
/embedor/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 — Stripe’s
hosted Checkout page refuses to load inside any iframe at all, so
paperplane.js’s iframe needsallow="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 noallowattribute 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/embedfrom scratch. - Prefill. A trigger button’s
data-template,data-address, anddata-classattributes are forwarded onto/embed’s query string when it opens — same?template=/?address=/?class=the standalone/sendpage 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 asdocument-level CustomEvents (paperplane:openedetc.) if you’d ratheraddEventListener. - 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 to turn it on for your embed.
- The address field’s “Use my location” button needs the iframe to grant
geolocation —
paperplane.jsandtemplate-embed.jsalready setallow="geolocation; payment"(template-embed.jsomitspayment, since it never reaches a card) on the iframe they create. If you build your own iframe wrapper around/embedor/templates/embed/{key}instead of using the provided script, add the sameallowvalues yourself or those features will silently fail (the browser blocks geolocation and payment in a cross-origin iframe by default).
Where hosts use it
- Publishers & advocacy — the reader tool beside the article (demo above).
- Legal & professional services — self-help letter libraries; certified mail is the standard courts expect.
- SMB portals — property management, billing, back-office “mail this notice” buttons that replace the post-office run.