EN

Why We Made Our Video Links Expire

Why JustRec's expiring links were a product decision: privacy, flat costs and delivery over hosting. The trade-offs, the objections and how expiry works in code.

Nicolas Ricardi6 min read

In short

  • We decided early that a JustRec link delivers a video; it doesn't host it. So links expire: 3 days on Lifetime, 30 days on Personal and Team.
  • Expiring links keep client data online only while it's needed, keep our storage costs flat, and stop us from turning into a video platform.
  • The server refuses an expired link the second it expires, and an hourly job deletes the video, subtitles and thumbnail from storage.
  • The trade-offs are real: no evergreen links, the occasional re-share, and a sentence on the page explaining what happened.

Our video links expire because we decided early that JustRec delivers videos; it doesn’t host them. A link exists so your client can watch a walkthrough this week, and once it has done that, keeping it online only adds risk and cost. Expiring links were in our product document before we wrote a line of server code.

We’ve explained the user’s side, and how to work with links that expire, in why expiring video links are safer than a permanent library. This is the other side: why we chose it, what it cost us, and how it works under the hood.

Delivery, not hosting

Most video tools for work are built around a library in the cloud. Your videos live there, links point into it, and the product grows by adding things to the library: folders, search, view counts, comments, embeds.

Think about what agencies and freelancers send to clients: round two of a homepage, a bug in a checkout, two minutes on a quote. Those videos have a reader and a deadline. The client watches, replies, and the next version replaces the old one. Nobody comes back to round two of the homepage in March.

So we framed JustRec as delivery. The recording, the library and the history live on your Mac. The server holds a copy for as long as the delivery takes, then lets it go. Every other decision follows from that one.

Our users’ clients didn’t sign up for a video archive

A screen recording of client work often shows client data: an admin panel, a customer’s name, a dashboard. The GDPR’s storage limitation principle says personal data should be kept no longer than necessary. A permanent link keeps it online indefinitely, on servers neither you nor your client picked. An expiring link turns “no longer than necessary” into a default instead of a chore. The GDPR and client videos article covers who is responsible for what.

Costs that stay flat

With a permanent library, every user costs more each month than the month before, forever. With expiring links, a user who shares the same amount every month costs the same every month. We did the full calculation in what it costs to host client videos on Cloudflare R2: a busy user on 30-day links settles around 5 GB of storage, while the same user with a permanent library passes 60 GB after a year and keeps growing.

That’s also what lets us sell a Lifetime license at all. A one-off payment can’t fund storage that grows for years. It can fund 3-day links.

Focus

If links don’t live forever, a whole category of features stops making sense: cloud folders, cloud search, view analytics, embeds for websites. We don’t have to build them, secure them or charge for them. JustRec doesn’t keep a cloud library, and that’s deliberate.

Why 3 days and 30 days

The two durations follow the plans:

How long a link lasts on each plan
Plan Link lasts Max video length Why
Lifetime 3 days 10 min Enough for feedback, reviews and bug reports; a one-off price can sustain it
Personal, Team 30 days 30 min Covers handovers, training and quotes the client rewatches before deciding

Plans and limits may change before launch.

Three days is enough for most review videos: the client watches the same day or the next. Thirty days covers the videos people come back to while they learn a new CMS or think about a proposal. Beyond a month, a video is reference material, and reference material deserves a proper home.

How expiry works on our server

The server is a Cloudflare Worker with Hono. Videos, subtitles and thumbnails live in an R2 bucket restricted to the EU; metadata lives in D1, also in the EU. Expiry is two separate mechanisms, on purpose.

  1. Upload completes

    The clock starts

    The server stores the expiry time: 3 days or 30 days from now, depending on the plan.

  2. Until expiry

    The link works

    Every request for the page, video, subtitles or thumbnail checks the expiry time first.

  3. The second it expires

    410 Gone

    The page says the link has expired and who to ask for a new one.

  4. Within the hour

    Files deleted

    An hourly job removes the video, subtitles and thumbnail from R2.

1. The link stops at the exact second. When an upload completes, the server sets expires_at to the current time plus the link’s lifetime. Every request for the page, the video, the subtitles or the thumbnail checks it first:

async function liveVideo(env: Env, id: string) {
  const row = await env.DB.prepare("SELECT * FROM videos WHERE id = ?").bind(id).first();
  const live = !!row && row.status === "ready" && (row.expires_at ?? 0) > now();
  return { row, live };
}

If it isn’t live, the page answers with HTTP 410 Gone and says the link has expired or was withdrawn, and asks the viewer to request it again from the sender, by name. 410 is the status that means the resource existed and was removed on purpose, which is exactly the case.

2. The files are deleted within the hour. A Cron Trigger runs the Worker every hour, at minute 17. It finds every video past its expiry, deletes the three objects from R2 and marks the row as expired:

async function cleanUp(env: Env) {
  const expired = await env.DB.prepare(
    "SELECT id FROM videos WHERE status = 'ready' AND expires_at <= ?",
  ).bind(now()).all<{ id: string }>();
  for (const { id } of expired.results) {
    await env.VIDEOS.delete([`videos/${id}.mp4`, `videos/${id}.vtt`, `videos/${id}.jpg`]);
    await env.DB.prepare("UPDATE videos SET status = 'expired' WHERE id = ?").bind(id).run();
  }
  // …then abort uploads that were started more than 24 hours ago and never finished
}

Withdrawing a link from the app skips the wait: the server deletes the files immediately and marks the row as revoked. Uploads that never completed are aborted after 24 hours, so half-uploaded videos don’t sit in storage either.

What stays behind, and why

We want to be precise about what “deleted” means:

  • Deleted from R2: the video, the subtitle file and the thumbnail.
  • Kept in D1: the row with the video’s id, title, sender name, size, duration, dates and status. That’s what lets the expired page tell your client whose video it was and who to ask.
  • Kept on your Mac: the original video and its subtitles, untouched.
  • In the viewer’s browser: the video response allows the browser to cache it privately for 10 minutes, so a tab that already loaded part of it may play that part briefly after expiry.

Why not R2 lifecycle rules?

R2 can expire objects on its own with lifecycle rules, by age and prefix. We didn’t use them for links because they work on object age, apply per rule rather than per video, and objects are typically removed within 24 hours of their expiration. We need the link to stop at an exact time, different lifetimes per plan, and immediate deletion when someone withdraws a link. A query on D1 and an hourly job do that in about twenty lines.

The objections, and our answers

We expect these, and some are fair:

Objections to expiring links
Objection Our answer What we accept
“My client opens the link late.” The page tells them who to ask, and sharing again takes one click from your library. An occasional re-share.
“I need a link for training material that lasts.” Use a video platform, a help center or your own site for long-lived videos. JustRec isn’t the right tool for those.
“Why can’t I choose ‘never’?” Then we’d be a hosting company with a growing bill and years of client data online. Some people will pick another tool.
“Three days is short.” It fits review rounds and bug reports, and keeps the Lifetime price possible. Personal and Team get 30 days. Lifetime users who send handovers will re-share more.

What we gave up

Being a delivery tool has a cost. There are no evergreen links to paste into a website. There are no view counts, because we don’t track viewers either. Some clients will see an expired page, and a few will be annoyed. We think those are the right trade-offs for videos that are, in the end, conversations. If you need the opposite, a permanent library with analytics, there are good tools for that, and our Loom alternative page compares the two approaches.

The idea underneath all of this is that your videos are yours and should live on your machine. We wrote about it in local-first screen recording.

Frequently asked questions

Why do JustRec links expire?

Because a client video is a message, not an archive. Expiring links keep the online copy only while the client needs it, which protects their data, keeps costs predictable and lets the price stay low.

How long do JustRec links last?

3 days on the Lifetime plan and 30 days on Personal and Team. You can withdraw a link earlier at any time, and the original video stays on your Mac.

What does my client see when a link has expired?

A page saying the link has expired or was withdrawn, with the sender's name and a request to ask them to send it again. The video, subtitles and thumbnail are no longer on the server.

Can I make a link that never expires?

No. For videos that should stay online for months, such as public tutorials or training libraries, a video platform or your own website is a better home. JustRec is built for videos that do their job in days.

How quickly is a video deleted after its link expires?

The link stops working at the expiry time. The files are deleted from storage by a cleanup job that runs every hour, so they're gone within about an hour of expiring.

Written by

Nicolas Ricardi

Maker of JustRec, at Nube

Nicolas makes JustRec at Nube, a web studio in Italy. He built it for the videos Nube sends to its own clients: design reviews, handovers and quotes explained in a few minutes instead of a meeting.

www.wearenube.dev
Build in public