Documentation
sendapage turns a brand template + a one-off brief into a personalized microsite at sendapage.com/p/<slug>. This guide covers the full surface — templates, briefs, pages, analytics, and the API.
Quickstart
Three steps from zero to a published page:
- Sign in at /login with GitHub or Google. You start on the Free plan with 5 lifetime pages.
- Get a template. Easiest path: clone a starter on /templates(post-meeting follow-up, deal-stage advance, event follow-up, outbound sequence step, proposal & pricing, renewal / QBR). Or chat with the AI to draft one at /templates/author. Or write one by hand at /templates/new.
- Generate a page at /pages/new: pick the template, drop in a brief, hit Generate. The microsite ships in ~60 seconds.
Send the link. Watch engagement land in the dashboard the moment the recipient opens it.
Templates
A template is the AI's brand handbook. It's reused across every page generated from it, so put effort into one good template — not into editing the AI's output afterwards.
Three ways to create a template
- Clone a starter — fastest. Six sales starters on /templates: post-meeting follow-up, deal-stage advance, conference & event follow-up, outbound sequence step, proposal & pricing, and renewal / QBR. One click clones into your workspace; edit afterwards however you like.
- Author with AI — interview-style chat at /templates/author. The AI asks about your brand, voice, audience, and CTA, then drafts the template for you. Live preview shows the draft as it builds; one click saves.
- Manual — start from a blank textarea at /templates/new. Most expressive, most work.
On Growth and above, the template editor can also import a brand kitfrom your website URL: logo, brand colours and fonts are pulled from the homepage so you don't have to type them in.
What goes in the instructions field
Treat it like onboarding a designer. Cover:
- Brand voice — formal, playful, technical, etc.
- Visual direction — colours, typography, layout density.
- Required sections — e.g. hero, value props, social proof, CTA.
- What NOT to include — e.g. no testimonials unless provided in the brief.
- The CTA — what action you want the recipient to take.
- Tailoring rules — for example, "if the recipient is a manager, lead with team-level outcomes; if an IC, lead with day-to-day workflow."
Analytics markup (data-track-section, data-cta="primary") is enforced globally — you don't need to mention it in templates.
Attaching template files
Upload files when creating or editing a template:
- Markdown / text / JSON / YAMLfiles drop into the agent's working directory next to the instructions — useful for canonical brand info, FAQs, structured product data, sample copy.
- Images, fonts, and other binary assets are placed in
./assets/<filename>and the agent will reference them directly in the generated HTML. URLs are rewritten at serve time so links work for the recipient.
Per-template caps: 20 files, 10 MB each. Images, SVG, fonts, PDF, markdown, JSON, YAML, plain text are all accepted.
Briefs
A brief is the case-specific input — who you're sending the page to and why. The agent personalizes the template using only what's in the brief plus any uploaded files. It won't invent dates, prices, names, or quotes.
Recommended structure
Recipient: Jane Smith, VP Procurement at Globex
Stage: Stage 3 (proposal review)
Key context:
- Renewal date 2026-09-15
- Current annual spend with us: $124k
- Previously raised concerns about onboarding ramp time
- Scoped expansion into APAC offices
Goals:
- Reaffirm the renewal value
- Pre-empt the ramp-time objection
- Open the APAC expansion conversationThe brief is a free-text field — markdown works, but plain prose is fine too. The clearer and more specific you are, the better the page.
One template, many briefs
One template + many briefs is the pattern. If you find yourself editing the template per recipient, you're fighting the system — move the variability into the brief.
CTAs & context
Two structured fields supplement the free-text brief.
CTAs (per-page)
Add labelled URLs the agent can pick from when emitting CTA anchors. The template decides which label to use for which audience; you supply the destinations.
{
"ctas": [
{ "label": "book", "url": "https://calendly.com/yourname/30min" },
{ "label": "video", "url": "https://www.loom.com/share/abc123" }
]
}Set in the dashboard form on /pages/new, or pass context.ctas on the API.
Free-form context
Anything else structured (CRM payload, deal data, computed fields) goes in context as JSON. The agent sees it in context.json alongside the brief and uses it where the template tells it to.
Page features
Tags
Free-text labels for organisation. Filter pages on the list view by tag chips. Set them when you create a page in the dashboard, or edit them later on the page detail.
Password protection
For confidential sales rooms / NDA-required pages. Set a password on the page detail (Settings → Password protection). Recipients see a password gate at /p/<slug> until they enter the password; their unlock is remembered for 4 hours via an HttpOnly cookie scoped to that slug.
Duplicate
On a ready page, "Duplicate" copies brief + template + context + tags and fires a fresh generation. Useful for trying a variant without editing the original.
Editing after generation
Click Edit on the page detail to tweak a generated page without regenerating it: hide a section, change a section's content, change CTA labels and links, or regenerate a single section with a hint ("shorter, lead with the SAML timeline").
Pre-send link check
The Check links card on the page detail tests up to 25 links on the page before you send it. Web links are fetched; mailto:, tel: and in-page anchors are checked for shape; javascript:, data: and relative links are flagged as invalid. Each link comes back ok, broken, unreachable, invalid or skipped (sites that refuse bots with 403/405/429 are skipped rather than reported broken).
Per-channel links
Next to Copy link, pick where you're sending the page (Email, LinkedIn, Sequence, Slack). The copied URL gets a utm_source tag (email, linkedin, sequence, slack), and visits are attributed to that channel, so lead reasons read like "via LinkedIn". Any other utm_sourceyou add yourself (for example a CRM's own tag) is shown as-is. All five utm_* parameters are stored on the visit.
Custom domains
Serve pages from your own subdomain, e.g. pages.yourcompany.com/p/<slug>. Add the domain at /domains, then create a DNS record CNAME → cname.vercel-dns.com. Once it verifies, page URLs in the dashboard, API responses, webhooks and HubSpot writeback use your domain. A custom domain only serves your own pages. Growth includes 1 domain, Scale 5, Enterprise unlimited.
Expiry
Pages don't expire. They stay live until you delete them (in the dashboard or with DELETE /api/v1/pages/:id).
Open Graph / share previews
Every page auto-generates an OG card image (clean black-on-white with the page title). Pasting /p/<slug> into Slack, iMessage, LinkedIn, X, or Gmail unfurls a proper preview without any setup.
Watermark
Free-tier pages display a small "Made with sendapage" mark in the corner. Paid tiers serve unbranded.
CSV import
For batch follow-ups (post-conference lists, CRM exports), drop a CSV at /pages/import. Each row becomes a page.
- No column mapping — the entire row is sent to the agent as JSON. Your template's instructions decide which fields matter.
- Optional "shared context" box — text applied to every row (sender, sign-off, default CTA, conference name, etc.).
- Caps: 200 rows / 2 MB per import. Each row is quota-checked atomically.
- Slug auto-derived from a name- or email-shaped column when present.
Analytics
Every published page ships with first-party tracking. No third-party cookies; visitor identifiers are page-scoped and rotate daily so visitors aren't trackable across different pages.
What we capture
- Views — server-rendered, captured even if JavaScript is blocked.
- Active time on page — only counts seconds the tab is visible & focused.
- Scroll depth — milestones at 25/50/75/100%.
- Section dwell — how long the visitor spent looking at each
data-track-section. - Link & CTA clicks — anchors with
data-cta="..."are bucketed as conversions. - Geo & org — country / city from the request, plus an IP-to-organisation lookup for the company name when available. Home and mobile connections resolve to the ISP, not the reader's employer.
- Channel —
utm_*parameters on the link (see per-channel links). - Engagement score (0–100) per session, shown in the dashboard: up to 30 points for active time (capped at 5 minutes), up to 25 for scroll depth, 25 for any CTA click, and 20 for a return visit.
- Live indicator — pulsing badge when someone is actively on the page right now.
Visitor IDs reset every UTC day, so "distinct people" is only counted within a day. Someone who comes back tomorrow looks like a new visitor.
Alerts
Slack, email and webhook alerts on opens, buying committees, prospect questions and a weekly digest. See Alerts.
Where to find it
On the page detail in the dashboard. Per-section heatmap, geography breakdown, sortable visitor table, and a real-time activity feed.
Alerts
Set up alerts at /alerts. Each rule has a trigger, a channel and a target, plus two optional filters.
Triggers
first_view(every plan) — a page is opened for the first time. Later visits are covered byrevivedandcommittee.committee(Scale and above) — several distinct people from the same company read one page on the same UTC day. Fires when the count reaches 2, then 3, then 5. Visitors whose network is an ISP, mobile carrier, cloud provider or VPN are ignored, because those don't identify a company.question(Growth and above) — a prospect sends a question through the Ask a question widget.cta_click(Growth and above) — a prospect clicks one of the page's buttons, such as Book a call. At most once per page every 15 minutes; the button name comes from the page itself.revived(Growth and above) — a page that went quiet for 5 or more days is opened again.weekly_digest(Growth and above) — a Monday summary (13:00 UTC) of up to five hot or warm leads from the last 7 days and up to five published pages nobody has opened yet. Skipped in a week with nothing to report.
Channels
- Slack — a Slack incoming-webhook URL.
- Email — any address.
- Webhook — any public URL on the default port; we POST JSON (below).
Slack and webhook targets that point at private, loopback or cloud-metadata addresses are rejected when you save the rule.
Scope and filters
- Account-wide or per-page. Leave the page empty and the rule covers every page you own, including pages created later by the API or HubSpot. Pick a page and it only fires for that page.
- Company match (
matchOrg) — only fire when the visitor's organisation name contains this text (case-insensitive), e.g.globex. Applies to the first view, committee and question triggers.
There are no score or time thresholds: a rule fires on its trigger, filtered only by page and company match.
What the alert says
Slack and email alerts are one line, followed by a "See what they read" link to the page's analytics in your dashboard:
First view: Renewal review for Globex was just opened by Globex Corp (Chicago)
Committee: 3 people at Globex Corp are reading “Renewal review for Globex” today
Question: Someone at Globex Corp asked a question on “Renewal review for Globex”: “Do you support SAML?”
Reply to: jane@globex.comWhen the organisation is unknown, the first-view line says "an unknown org" and the question line says "A prospect". The location is the city, falling back to the country. The "Reply to" line only appears when the prospect left an email.
Webhook payloads
Webhook alerts POST the raw event as JSON. pageUrlis the page's dashboard URL, not the public link.
// first_view
{
"event": "page.first_view",
"pageId": "8e2a...",
"pageTitle": "Renewal review for Globex",
"pageUrl": "https://sendapage.com/pages/8e2a...",
"org": "Globex Corp",
"country": "US",
"city": "Chicago",
"referrer": null
}
// committee
{
"event": "page.committee",
"message": "3 people at Globex Corp are reading “Renewal review for Globex” today",
"visitorsToday": 3,
"pageId": "...", "pageTitle": "...", "pageUrl": "...",
"org": "Globex Corp", "country": "US", "city": "Chicago"
}
// question
{
"event": "page.question",
"message": "Someone at Globex Corp asked a question on “Renewal review for Globex”: “Do you support SAML?”",
"question": "Do you support SAML?",
"replyTo": "jane@globex.com",
"pageId": "...", "pageTitle": "...", "pageUrl": "...",
"org": "Globex Corp", "country": "US", "city": "Chicago"
}
// cta_click
{
"event": "page.cta_click",
"message": "Someone at Globex Corp clicked “Book a call” on “Renewal review for Globex”",
"cta": "Book a call",
"pageId": "...", "pageTitle": "...", "pageUrl": "...",
"org": "Globex Corp", "country": "US", "city": "Chicago"
}
// revived
{
"event": "page.revived",
"message": "Someone at Globex Corp reopened Renewal review for Globex after 12 quiet days",
"quietDays": 12,
"pageId": "...", "pageTitle": "...", "pageUrl": "...",
"org": "Globex Corp", "country": "US", "city": "Chicago"
}
// weekly_digest
{
"event": "digest.weekly",
"subject": "2 hot leads to follow up this week",
"leads": [
{ "pageId": "...", "title": "...", "heat": "hot", "score": 74,
"reasons": ["Opened 3× in the last 24h", "clicked Book a call"] }
],
"generatedAt": "2026-10-26T13:00:00.000Z"
}Any field can be null when we don't know it. The digest's leads array can include cool leads; the Slack and email versions only list hot and warm ones. Alert webhooks are sent once, without retries, and are signed (see Verifying signatures).
Ask a question
On Growth and above, every published page gets a small "💬 Ask a question" button in the bottom-left corner. It opens a short form: the question (3–1,000 characters) and an optional email for a reply. It works without JavaScript, and after sending, the prospect sees a short thank-you note in its place.
Each question:
- is saved against the visitor's session,
- fires your
questionalert rules (with the reply-to email, if given), - is written to
sendapage_last_questionon the HubSpot deal for HubSpot-created pages, - and counts towards the page's ranking in Hot right now.
The widget only appears on real, tracked visits (not your dashboard preview or link unfurls). Password-protected pages require the visitor to have unlocked the page. Each visitor can send up to 5 questions per page every 10 minutes, and there's a honeypot field for bots. If you drop below Growth, the widget disappears from your pages.
Selling tools
Dashboard features built on the analytics. None of them need any integration work.
Hot right now (Growth+)
/leadsranks your pages by recent engagement across the last 14 days and labels each one hot, warm or cool, with a plain-English reason line, e.g. "Reading right now · asked a question · clicked Book a call · came back on 2 days · via LinkedIn". The dashboard home shows the top three.
AI follow-up drafts (Growth+)
From a page's detail or Hot right now, draft a short follow-up email based on the brief and what the prospect actually read and clicked. Add an optional note to steer it ("mention the Q3 pilot slot"). You get a subject and body to copy; nothing is sent for you.
Accounts (Scale+)
/accounts groups the last 30 days of visits by company: every page that company read, the peak number of people seen on a single day, total reading time, CTA clicks and last visit. ISP, mobile, cloud and VPN networks are left out.
Weekly digest (Growth+)
A Monday summary of who to follow up with and which pages nobody has opened yet. Turn it on by creating a weekly_digest rule on /alerts.
Template performance (Growth+)
The /templates list shows, per template: pages sent (published), open rate, CTA click rate among opened pages, and average reading time. Use it to see which messaging gets read.
API reference
Use the API to manage templates, generate pages, and read status from any backend (CRM, ATS, automation tooling). API access is included on every paid plan (Starter and above).
Authentication
Create a key at /api-keys. Pass it in the Authorization header.
Authorization: Bearer sap_live_xxxxxxxxxxxxxxxxxxxxTemplates
Full CRUD. Asset URLs you supply are server-side fetched into our blob storage (so the agent can read them at generation time) — must be public HTTPS.
# Create
curl -X POST https://sendapage.com/api/v1/templates \
-H "Authorization: Bearer $SAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "OutboundCon 2026 — booth follow-up",
"description": "Personalized post-booth follow-up.",
"instructions": "# Brand voice\n...",
"asset_urls": [
{ "url": "https://example.com/logo.svg", "name": "logo.svg" }
]
}'
# List
curl https://sendapage.com/api/v1/templates -H "Authorization: Bearer $SAP_KEY"
# Get / update / delete
curl https://sendapage.com/api/v1/templates/$TEMPLATE_ID -H "Authorization: Bearer $SAP_KEY"
curl -X PATCH https://sendapage.com/api/v1/templates/$TEMPLATE_ID \
-H "Authorization: Bearer $SAP_KEY" -H "Content-Type: application/json" \
-d '{ "instructions": "# Updated voice\n..." }'
curl -X DELETE https://sendapage.com/api/v1/templates/$TEMPLATE_ID -H "Authorization: Bearer $SAP_KEY"Pages
Create / list / get / delete. Page creation returns immediately with status queued; generation runs in the background.
# Create
curl -X POST https://sendapage.com/api/v1/pages \
-H "Authorization: Bearer $SAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "31058a46-73bf-4488-a52a-728b73465e09",
"brief": "Recipient: Jane Smith @ Globex...",
"context": {
"deal_id": "OPP-1234",
"ctas": [{ "label": "book", "url": "https://calendly.com/me/30min" }]
},
"slug_hint": "globex-q1",
"webhook_url": "https://your-app.com/sendapage-callback"
}'
# Poll / list / delete
curl https://sendapage.com/api/v1/pages/$PAGE_ID -H "Authorization: Bearer $SAP_KEY"
curl https://sendapage.com/api/v1/pages -H "Authorization: Bearer $SAP_KEY"
curl -X DELETE https://sendapage.com/api/v1/pages/$PAGE_ID -H "Authorization: Bearer $SAP_KEY"Page response shape
{
"id": "8e2a...",
"slug": "globex-q1-9td4hk7hrt",
"status": "ready",
"title": "Renewal review for Globex",
"url": "https://sendapage.com/p/globex-q1-9td4hk7hrt",
"created_at": "2026-05-03T07:38:49.000Z",
"published_at": "2026-05-03T07:39:42.000Z",
"published": true,
"requires_review": false,
"error": null,
"error_category": null,
"source_event": null,
"idempotency_key": null
}Statuses: queued → generating → ready (or failed with error and, when known, error_category populated). url uses your verified custom domain when you have one. source_event and idempotency_key are returned by create and list, not by GET /api/v1/pages/:id.
Idempotency and source tagging
Send an Idempotency-Key header on POST /api/v1/pages and a retry with the same key returns the existing page (200) instead of creating a second one. Pass an optional source_event object (source, trigger, external_id, user_label, plus any extra fields) to record what fired the page, such as a CRM stage or sequence step.
POST /api/v1/pages
Idempotency-Key: opp-1234-stage-3
{ "template_id": "...", "brief": "...",
"source_event": { "source": "salesforce", "trigger": "stage_change", "external_id": "OPP-1234" } }Quota headers
Every POST /api/v1/pages that creates a page (202) returns your quota so automations can see the limit coming:
x-quota-limit: 500 # or "unlimited"
x-quota-used: 132 # this billing period (lifetime pages on Free)
x-quota-remaining: 368 # omitted when the limit is unlimitedIdempotent replays (200) don't carry these headers.
Manual review before publish
For CRM-driven flows where the AE should eyeball a page before the link goes out, pass publish: "manual" on creation. The page generates as normal, but published_at stays null and the public /p/<slug>URL serves a neutral "preparing your page" holding page until the owner clicks Publish in the dashboard. Webhook fires when generation completes either way — check requires_review on the payload to decide whether to forward the URL or route it to a human for approval.
// Request — same shape as above with one extra field
{
"template_id": "...",
"brief": "...",
"publish": "manual"
}
// Response while awaiting review
{
"status": "ready",
"published_at": null,
"published": false,
"requires_review": true
}Errors
401 invalid_api_key— bad / revoked key402 api_access_not_included— the API is on every paid plan; upgrade from Free402 quota_exceeded— page limit hit (monthly on paid plans, lifetime on Free). Watch the quota headers to warn before you get here.402 template_limit_reached— plan's template count maxed404 template_not_found/404 not_found413 asset_too_large— over 10 MB422 asset_fetch_failed/422 asset_mime_not_allowed400 invalid_json— body isn't JSON400 invalid_payload— malformed body; details indetails
Webhooks
Pass a webhook_url on page creation. We POST a JSON payload when generation finishes (success or failure):
{
"event": "page.completed",
"page": {
"id": "8e2a...",
"slug": "globex-q1-9td4hk7hrt",
"status": "ready",
"title": "Renewal review for Globex",
"url": "https://sendapage.com/p/globex-q1-9td4hk7hrt",
"created_at": "...",
"published_at": "...",
"published": true,
"requires_review": false,
"error": null,
"error_category": null,
"source_event": null,
"idempotency_key": null
}
}page has the same shape as the create / list response. Any non-2xx response or network error is retried, up to 3 attempts in total with backoff, and every attempt is logged at /webhooks. Build the receiving endpoint to be idempotent on page.id. Private, loopback and cloud-metadata addresses and non-default ports are blocked.
For engagement events (opens, questions, buying committees), use webhook alerts.
Verifying signatures
Every webhook we send (page callbacks, alert webhooks and the weekly digest) carries an X-Sendapage-Signature header: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw request body> using your signing secret. Your secret is on the Webhooks page. Verify against the raw body (before parsing), and reject timestamps more than five minutes old.
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}HubSpot
Generate a page when a HubSpot deal reaches a stage, and write the link and engagement back onto the deal. No API code needed. CRM integrations are included on Growth and above.
1. Connect
- In HubSpot: Settings → Integrations → Private Apps → Create a private app.
- Give it these scopes:
crm.objects.deals.read,crm.objects.deals.write,crm.schemas.deals.write,crm.objects.contacts.read,crm.objects.companies.read. - Paste the access token at /integrations. On connect we create a "sendapage" property group and the deal properties listed below.
- Choose the template HubSpot pages are generated from.
- Choose what happens when a page is generated: Hold for rep review, then publish (the default) or Publish immediately. Held pages work like
publish: "manual"on the API.
2. Trigger it from a workflow
- Automation → Workflows → create a deal-based workflow.
- Set the enrolment trigger, e.g. deal stage is "Meeting completed".
- Add a Send a webhook action, method POST, to the secret URL shown on /integrations (
/api/hooks/hubspot/<secret>). Include the deal's Record ID in the request body.
The secret in the URL is the credential, so keep it private. Rotate URL on the integrations page issues a new one and the old URL stops working immediately.
What happens on each webhook
- We read the deal (name, amount, stage, pipeline, close date, description), its first associated contact and company, and up to 5 recent notes, and turn them into the brief. The raw records are passed as
context.hubspot. - Idempotent per deal + stage. HubSpot retries and re-enrolment in the same stage return the existing page (
200,duplicate: true) instead of generating a new one. Moving the deal to a different stage creates a new page. - A new page returns
202withpage_idand counts against your page quota. - The page is written back to the deal when generation finishes, and again when a held page is published.
Errors are shown as "Last error" on the integrations page: 402 crm_integrations_not_included_in_plan (below Growth), 402with the quota message when you're out of pages, 409 no_template_selected / template_not_found, 400 deal_id_missing (Record ID not in the body), and 401 / 502when HubSpot rejects the token or can't be reached.
Deal properties we write
sendapage_url— the public page link (on your custom domain if you have one).sendapage_status—generating,needs_review,publishedorfailed.sendapage_first_viewed_at— set when a new visitor opens the page. Visitor IDs reset daily, so later opens by new people (or on later days) update it.sendapage_last_question/sendapage_last_question_at— the latest Ask a question submission and when it arrived.sendapage_cta_clicks— total CTA clicks on the page.sendapage_last_cta— the label of the last CTA clicked, e.g. "Book a call".
Build a second workflow on these, for example "when sendapage_statusis published, email the contact the link" or "when sendapage_last_question_atis known, create a task for the deal owner".
Other integrations
Google Forms / Typeform / any form tool
Wire a form to auto-generate a sendapage on submission via Apps Script:
const SAP_API_KEY = 'sap_live_...';
const SAP_TEMPLATE_ID = 'your-template-uuid';
function onFormSubmit(e) {
const v = e.namedValues || {};
const f = (k) => (v[k] && v[k][0]) || '';
const name = f('Full Name') || f('Name');
const email = f('Email');
if (!name || !email) return;
const brief = ['Recipient: ' + name, 'Email: ' + email,
'', 'Notes:', f('What did you want to chat about?') || ''].join('\n');
const res = UrlFetchApp.fetch('https://sendapage.com/api/v1/pages', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + SAP_API_KEY },
payload: JSON.stringify({
template_id: SAP_TEMPLATE_ID, brief,
context: { recipient: { name, email } },
}),
});
// Optionally write back the URL into the linked Sheet from JSON.parse(res.getContentText()).url
}Other CRM stage triggers
HubSpot has a native integration. For other CRMs (Salesforce Flow, Pipedrive automations, etc.), have the stage change call your own backend or automation tool, which then calls POST /api/v1/pageswith the deal's rep + prospect data as the brief. Send an Idempotency-Key such as <deal id>:<stage> so retries don't create duplicates, and use webhook_url to write the generated URL back onto the deal.
Examples
Three end-to-end sales workflows wired up with the API. Each shows the trigger, the call, what the AE sees, the generic email it replaces, and the back-of-envelope ROI.
1. Post-meeting follow-up
The CRM marks a discovery call as "meeting completed". Instead of the AE scrambling to write a personalized recap from notes, the stage change fires a webhook into sendapage. A tailored recap page is live before the rep is out of their next call — and the AE pings the moment the prospect opens it.
curl -X POST https://sendapage.com/api/v1/pages \
-H "Authorization: Bearer $SAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "5b1d0c2e-8f4a-4c1e-9a7b-2d6e3f9a1c40",
"brief": "Recipient: Jane Smith, VP Procurement at Globex\nMeeting: 2026-05-15 discovery, 45 min\nAttendees (theirs): Jane Smith, Raj Patel (IT)\nAttendees (ours): Tom Reed (AE), Lin Park (SE)\nKey points discussed:\n- Renewal date 2026-09-15; current annual spend $124k\n- Concern: onboarding ramp time hit them in 2024\n- Interest: APAC expansion, ~140 new seats H2\nNext step agreed: technical deep-dive with Raj 2026-05-22\nOpen questions to address on the page:\n- SAML rollout timeline\n- APAC data residency",
"context": {
"deal_id": "OPP-1234",
"ctas": [
{ "label": "book_deepdive", "url": "https://calendly.com/tom-reed/45min" },
{ "label": "view_proposal", "url": "https://sendapage.com/p/globex-q1-proposal" }
]
},
"slug_hint": "globex-q1-recap",
"webhook_url": "https://crm.example.com/sendapage-callback"
}'What the AE sees
- The CRM callback at
webhook_urlreceives the page URL about 60s after the call, ready to drop into the follow-up email. - Slack alert when Globex opens it: "Recap for Globex — APAC + renewal was just opened by Globex (Chicago)", with a "See what they read" link.
- Dashboard shows section dwell: APAC expansion 1m48s, SAML 24s, pricing 6s.
Before vs after
Generic follow-up email
Subject: Great chatting today
Hi Jane,
Thanks for the time today — really enjoyed
the conversation. As discussed, I'm attaching
our latest deck and a one-pager on the
platform. Let me know if any questions come
up, and I'll get time on the calendar for a
technical deep-dive with Raj next week.
Best,
Tomsendapage call + result
POST /api/v1/pages → 202 queued
Webhook fires ~60s later:
url: sendapage.com/p/globex-q1-recap
title: "Recap for Globex — APAC + renewal"
Email body becomes 3 lines:
"Jane — quick recap of today, including
the APAC notes Raj asked for:
sendapage.com/p/globex-q1-recap"
First-open alert fires to Tom in Slack with
a link to the page's analytics, where he
sees per-section dwell.ROI (assumptions): AE saves ~12 min per recap (writing time + asset wrangling). First-open alert lets the AE follow up while the meeting is still top of mind for the buyer — we hypothesise a ~1.4x reply rate uplift on opened recaps. Treat both numbers as directional until you measure your own.
(Live sample available once your account is set up — see Quickstart.)
2. Conference / event follow-up
You walk off the OutboundCon floor with 200 booth scans in a CSV. The classic outcome is a 7-day delay, a generic "great to meet you" email, and a 4% reply rate. With sendapage, drop the CSV at /pages/import or POST each row to the API and every prospect lands on a page that mirrors the conversation you actually had.
curl -X POST https://sendapage.com/api/v1/pages \
-H "Authorization: Bearer $SAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "a7c3e915-2b6d-4f08-8e1a-6c9d4b2f7e13",
"brief": "Recipient: Priya Iyengar, RevOps Lead at Runbeam\nEmail: priya.iyengar@runbeam.dev\nBooth interaction (notes): Came back twice — first skeptical, second after the 2pm panel demo. All-in on automation. Wants to call the API from Salesforce flows. Sharp questions on webhooks, idempotency, rate limits. Probably the highest-intent visitor of the day.\nAngle for the page:\n- Lead with the Salesforce Flow → API → page pattern\n- Address webhook idempotency and rate limits up-front\n- Offer the 30-min technical walkthrough as the primary CTA",
"context": {
"event": "OutboundCon 2026",
"row": {
"email": "priya.iyengar@runbeam.dev",
"full_name": "Priya Iyengar",
"role": "RevOps Lead",
"company": "Runbeam"
},
"ctas": [
{ "label": "book_walkthrough", "url": "https://calendly.com/ae/30min?utm_source=outboundcon" }
]
},
"slug_hint": "outboundcon-runbeam-priya"
}'What the AE sees
- One page per scan, each with its own URL.
- Slack alert from an account-wide first-view rule: "OutboundCon follow-up for Runbeam was just opened by Runbeam (Austin)".
- Hot right now ranks every opened page by engagement, so by Friday the AE knows which 12 of the 200 scans are warm and worth a personal call.
Before vs after
Generic post-event blast
Subject: Great meeting you at OutboundCon
Hi {{first_name}},
It was great chatting with you at the
OutboundCon booth this week. I've attached
our deck — let me know if you'd like to
set up a follow-up call to discuss how
{{company}} could benefit from our platform.
Cheers,
The Acme teamsendapage call + result
CSV (200 rows) → /pages/import
Each row generates a personalized page in
~60s. Brief includes booth-conversation
notes from the "notes" column.
Email body per recipient:
"Priya — picking up the Salesforce Flow
thread from Tuesday's booth chat. Built
this for you: sendapage.com/p/outboundcon-
runbeam-priya"
Hot right now ranks the opened pages by
engagement so the AE works the warm tail
first.ROI (assumptions): 200 scans manually triaged + personalized = ~10 hours of AE time. CSV import + sendapage compresses that to ~20 minutes of QA. Bigger win is signal: the AE stops cold-calling the bottom 80% and spends the saved cycles on the most-engaged 20%. Reply-rate uplift is workflow-dependent — measure against your last event.
3. Outbound sequence step
Step 3 of an outbound sequence usually ships the same templated paragraph to every prospect. Replace that step's body with a per-prospect sendapage link generated from the lead record. The sequence tool fires a webhook to your backend; your backend POSTs to sendapage and writes the URL back into the merge field. The email goes out with a real, tailored landing page behind it.
curl -X POST https://sendapage.com/api/v1/pages \
-H "Authorization: Bearer $SAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "e2f8b4d1-9c3a-4e57-b6a0-1d5c8f2e9b74",
"brief": "Recipient: Darius Okonkwo, VP Sales at Finlattice\nRole: VP Sales, team of 18 SDRs/AEs targeting mid-market fintech\nPrior outreach: Step 1 (intro, opened 2x, no reply), Step 2 (case study link, no open)\nSegment signal: Evaluated Mutiny and Qwilr — said both ""felt like marketing tools, not sales tools""\nAngle for this page:\n- Lead with ""sales-team-owned, not marketing-team-owned""\n- One short case study from a 20-AE fintech team (use the Linnea case in assets)\n- Soft CTA: 15-min walkthrough, not a demo",
"context": {
"sequence_id": "seq_fintech_vp_q2",
"step": 3,
"ctas": [
{ "label": "book_15min", "url": "https://calendly.com/ae/15min?utm_source=seq_step3" }
]
},
"slug_hint": "finlattice-darius"
}'What the AE sees
- Sequence tool sends the step-3 email; merge field already contains the sendapage URL.
- A per-page or account-wide first-view alert pings the AE the moment Finlattice opens it. Add a company match (
matchOrg:finlattice) to only hear about opens from their network. - The
utm_source=seq_step3tag shows up as the channel in lead reasons, and template performance on /templates compares open and CTA rates across step templates, feeding the next iteration.
Before vs after
Generic sequence step 3
Subject: Quick thought, {{first_name}}
Hi {{first_name}},
Wanted to share a case study from one of
our customers in {{industry}} — they saw
a 38% lift in pipeline within 90 days.
Worth a quick chat?
[generic_case_study.pdf attached]sendapage call + result
Sequence step 3 fires webhook →
backend calls POST /api/v1/pages →
webhook_url writes back URL into the
sequence's merge field {{sap_url}}.
Email body:
"Darius — saw you're evaluating tooling
for the team of 18. Built this for you,
one short read: {{sap_url}}"
First open pings the AE in Slack; Hot
right now ranks the opens by engagement so
the AE calls the most engaged first.ROI (assumptions):A 200-prospect sequence at step 3 sees ~30% opens and ~3% replies on a generic body. Per-prospect pages don't change the open rate (it's a subject-line game) but first-view alerts plus Hot right now let the AE intervene on the ~10 high-intent opens in the same hour they happen, instead of 24-48 hours later. We hypothesise a 1.4x reply rate on opened follow-ups; validate against your own sequence baseline.
Pricing & quotas
See the pricing table on the landing page for current tiers. Highlights:
- Free— 5 pages (lifetime, not monthly), 1 template, 1 seat, "Made with sendapage" watermark, no API.
- Starter $39/mo — 100 pages/mo, 3 templates, 1 seat, API access, no watermark.
- Growth $79/mo — 500 pages/mo, 10 templates, 3 seats, 1 custom domain, HubSpot integration, plus Hot right now, AI follow-up drafts, brand kit import, the Ask a question widget, question alerts, the weekly digest and template performance.
- Scale $149/mo — 2,000 pages/mo, unlimited templates, 5 seats, 5 custom domains, everything in Growth plus buying-committee alerts and Accounts.
- Enterprise — from $1,500/mo: unlimited pages (fair use), unlimited templates and custom domains, every feature, negotiated seats. Talk to sales.
First-view alerts, CSV import, password protection and the API (on paid plans) aren't tier-gated beyond that. Page limits are hard caps: once you reach yours, new pages are refused with 402 quota_exceeded until the monthly count resets (on Free, until you upgrade). Manage your subscription at /billing.
FAQ
How long does generation take?
Typically 30–90 seconds. Templates that ask the agent to do brand research take longer.
Which AI model is used?
sendapage runs Claude Code in a sandbox against an Anthropic-compatible endpoint. The model is set workspace-wide and tuned by the operator — there's no per-template or per-user model picker. We've picked a model that hits the right quality/cost tradeoff for personalized HTML; we change it as the frontier moves.
Can I edit the generated HTML?
Yes, for small changes. Edit mode on the page detail lets you hide sections, change a section's content, change CTA labels and links, or regenerate one section with a hint. For bigger changes, refine the template or brief and use "Duplicate" to generate a fresh version.
Can I use my own domain?
Yes, on Growth (1 domain), Scale (5) and Enterprise. Add it at /domains and point a CNAME at cname.vercel-dns.com. See custom domains.
Do generated pages expire?
No. A page stays live until you delete it.
Can I require a password to view a page?
Yes. On the page detail, Settings → Password protection. Visitors see a password form before the page; an unlock cookie remembers them for 4 hours.
Is the data private?
Briefs, templates, and generated pages are visible only to your account. Visitor analytics use a daily-rotating salted hash, scoped per-page — visitors aren't identifiable across different pages and cannot be re-identified after the daily rotation.
Something missing? Email hello@sendapage.com.