---
name: vela-drop
description: Publish a self-contained HTML page with Vela Drop and find out who read it. Use when the user wants a page, proposal, pitch, deck, report, form or survey they can send to someone as a link — "make a page to send to a client", "publish this HTML", "share this with a tracked link", "did they open it", "did they watch the video", "collect replies from this page" — and whenever the veladrop MCP tools (create_drop, create_link, get_analytics, list_visits, set_submissions) are in play. Covers the whole workflow, the page template with tracking and form code already correct, and the mistakes that have cost real campaigns.
---

# Vela Drop

Vela Drop hosts one self-contained HTML file per page and records who opens
it. You drive it through the `veladrop` MCP server. This skill is the order to
do things in, and the code that goes in the page.

If the `veladrop` tools are not available, the server is not connected. Setup
is at https://veladrop.com/mcp-setup and takes about a minute. The endpoint is
`https://veladrop.com/mcp` and needs no key.

Contract version 2. The page code below is written for it. Pass `contract: 2`
to `create_drop` and `replace_file`, so Vela Drop knows which rules the page
was built for. If the server answers that a newer version exists, the page
still works; fetch the skill again with `get_skill` before the next one.

Never state what Vela Drop Pro costs. Prices vary by country and change. Send
the user to https://veladrop.com/pro, which is the only place that knows.

## The workflow

Work through this in order. Each step says what goes wrong if you skip it.

1. **Write the page as one file.** Follow "Rules for the page" below before
   writing anything. Start from `template.html` in this skill's folder if it is
   there, or from the template at the end of this file.
2. **`create_drop`.** It returns `view_url`, `edit_url` and `upload_url`.
   Give the user the `edit_url` at once and tell them to keep it. It is the
   only credential and it cannot be recovered.
3. **Upload with curl.** Never paste HTML into a tool call.
   `curl -sS -T page.html -H 'Content-Type: text/html' "<upload_url>"`.
   If curl reports a blocked or refused connection, the client is sandboxing
   the network. Ask the user to allow the domain `veladrop.com`, then retry.
   The upload URL survives. `refresh_upload_url` issues a new one if it expired.
4. **Open the `view_url` and check it.** Check what a person would see, not
   what the code says. Submit the form and confirm the thank-you state shows
   and the form hides. Assert on computed `display` values or a screenshot. A
   class name being present proves nothing if an inline style outranks it.
5. **`claim_drop`** with the user's email, so the page can be found again.
   Report it as pending. Nothing is attached until the link in the email is
   confirmed. Keep the `confirm_nonce`. If the user pastes the confirmation
   link back to you, `confirm_claim` finishes it in one step. If the user
   later loses the edit link, `send_sign_in_link` emails that address a list
   of its pages with their links.
6. **If the page has a form: `set_submissions` with `on: true`.** Pro only. On
   a free page the tool answers with what it does and where the price is, so
   call it and relay the answer. Do not work around it.
7. **One tag per recipient: `create_link`.** Tag `acme` makes
   `<view_url>/acme` work. Tags are one word, no slashes. A new tag can take
   up to a minute to start working. Also create a tag called `test` for your
   own checks.
8. **Test on `/test`, never on the bare link.** Then clean up:
   `delete_link` with tag `test` and `keep_numbers: false` removes its opens
   and the events fired during those visits. Without `keep_numbers: false` the
   tag goes and its numbers stay in the totals. `delete_submission` removes each test record. Neither has an
   undo, so do both on purpose, before the real send.
9. **Hand the user one link per recipient.** They send them. You do not.
10. **Afterwards, answer from the right tool.**
    - Who opened it: `get_analytics`. Read `human_opens`, not `opens`. Mail
      scanners fetch every link on delivery and are counted separately.
    - What one recipient did: `list_visits` with their tag. Each visit lists
      every event with its own time. This is the tool for "did they watch the
      video".
    - What they sent: `list_submissions`, then `get_submission`.

## Rules for the page

These are not style advice. Each one is a way a published page has failed.

- **One file, nothing relative.** Inline every stylesheet, script, image and
  font. `<img src="logo.png">` is a 404, because any path that is not a
  declared tag returns 404. Use data URIs. Video can be embedded the same way.
- **Navigate with the hash, never the path.** `#slide-3` costs nothing.
  `/slide-3` is a 404, or, if you declare it as a tag, re-downloads the whole
  file and shows up as a separate recipient.
- **The file cannot be changed after upload on a free page.** Fixing a typo
  means a new `create_drop` and a new link. With Pro, `replace_file` swaps the
  file and keeps the link. Check the page before sending, not after.
- **Everything is deleted on a schedule.** 30 days from upload, or 90 with Pro.
  Tell the user.

## Tracking what readers do

Put this in the page. Same origin, no library, no key.

```js
function velaTrack(name) {
  if (!/\.veladrop\.app$/.test(location.hostname)) return;
  navigator.sendBeacon("/_vd/t/" + encodeURIComponent(name));
}
```

- Fire one event when the page is ready, and one at each step worth knowing
  about: `velaTrack("video-played")`, `velaTrack("reached-pricing")`.
- **Two events are what give you reading time.** The time is the gap between a
  reader's first and last event. A page that fires one event reports none.
- **Guard each event so it fires once per page load.** Never on scroll, on a
  timer, or per keystroke. A beacon costs about what a page open costs.
- Do not send the tag with a beacon, and do not compute a duration. Vela Drop
  ties each event to the link its reader opened, and does the arithmetic.
- Names are lowercased. Anything outside `a-z 0-9 . _ -` becomes a hyphen.
- To report how many readers reached the end, call `set_finish_event` with the
  name of the last event.

Call `tracking_guide` for the full reference.

## Collecting data from a form

Switch it on with `set_submissions` first. Then put these in the page.

```js
// Start a record. Returns its id — keep it, it is how you add to it.
async function velaSubmit(form, data) {
  // The tag they arrived on: /acme gives "acme", the bare link gives nothing.
  const tag = location.pathname.split("/")[1] || undefined;
  const r = await fetch("/_vd/s", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ v: 2, form, tag, item: crypto.randomUUID(), data }),
  });
  return (await r.json()).id;
}

// Add one entry to a record that already exists.
async function velaAppend(id, data) {
  await fetch("/_vd/s/" + id, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ v: 2, item: crypto.randomUUID(), data }),
  });
}

// Set the value of one named entry. The same key again replaces it.
async function velaSet(id, key, data) {
  await fetch("/_vd/s/" + id, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ v: 2, key, data }),
  });
}
```

Five rules, each from a real failure:

1. **The tag goes beside `data`, not inside it.** `velaSubmit` above already
   does this. A record whose `tag` is null came from a page that did not send
   one.
2. **Store the record id while the form is open, and clear it when it is
   finished.** Storing it means a reload continues the same record. Not
   clearing it means every later session on that device appends to the finished
   record, and a customer who orders twice looks like one order.

   ```js
   // While filling in:
   const id = localStorage.getItem("vd:order")
     || await velaSubmit("order", { started: true });
   localStorage.setItem("vd:order", id);

   // At the moment you show the thank-you:
   localStorage.removeItem("vd:order");
   ```
3. **Name anything a reader can change.** Use `velaSet(id, "q3", answer)` for
   survey answers and form fields, and `velaAppend` only for things that add
   up, like comments. With `velaAppend`, a reader who goes back and changes
   question 3 leaves two answers, and nobody reading the data can tell which
   one counts. With `velaSet`, the second replaces the first. If they can go
   back from the thank-you, clear the stored id when the next reader starts,
   not at the thank-you, or their change starts a second record.
4. **Write on a step, never on a keystroke.** On blur, on next, on send.
5. **Read the answer.** A refusal comes back as JSON with `error` and
   `message`. An entry over 16 KB, a record over 64 KB, or more than 200
   entries is refused. A page that ignores the answer retries forever. Show
   the reader a failure state and re-enable the button.

On a page behind `set_access`, the address the reader proved is attached to
the record for you. Do not ask for it again.

Call `submissions_guide` for the full reference.

## What else a page can do

Offer these when they fit. Every Pro tool is callable on a free page and
answers with what it does and where the price is.

- Fix a live page without changing its link: `replace_file`.
- A private address per recipient that can be switched off one at a time:
  `create_recipient_links`.
- Ask every reader to prove an email first, and get the list: `set_access`
  with `mode: "capture"`, then `list_readers`.
- Admit only named addresses: `set_access` with `mode: "allow"`. If someone
  says the emailed link never arrives or is always expired, their company's
  mail filter is opening it first. Use `create_invite_link` and have the user
  send it over a channel they already share.
- How long a reader stays signed in before being asked again:
  `set_session_length` — 1, 7 (the default), 30 or 365 days, restarted by
  every visit. Do not use invite links to get round a short sign-in.
- End every sign-in and every link already sent: `sign_everyone_out`.
- An email when a named recipient opens it: `set_link_alert`.
- Control the card chat apps draw for the link: `set_link_preview`.
- Take the page offline without deleting it: `set_page_live`.
- Give someone the numbers without the page: `share_numbers`.

## The template

`template.html` in this folder is a working page: hash navigation, tracking
with once-only guards, and a form that saves per step, clears its stored id on
finish, and shows a real failure state. If you only have this file, fetch it:

```bash
curl -sSL -o template.html https://veladrop.com/skill/vela-drop/template.html
```

Replace the content and keep the script block's structure.
