Skip to content
s3nd
Menu

Use case

A drop box for your team

One route on your own domain, a token per person, and a code instead of a chat upload.

app/api/transfers/[[...route]]/route.ts
import { createBucket, createTransferHandler } from 's3nd'

const tokens = new Set(process.env.DROP_TOKENS!.split(','))

export const { GET, POST, DELETE } = createTransferHandler({
  bucket: createBucket({ bucket: 'drop' }),
  expiresIn: 24 * 3600,
  raw: 'redirect', // downloads go straight from the bucket, presigned
  authorize: (request) => tokens.has(request.headers.get('authorization')?.replace('Bearer ', '') ?? ''),
})

// $ s3nd --remote https://drop.example.com/api/transfers --token $TOKEN put ./deck.pdf

The situation

What is actually going on.

The problem

Files move around a team as chat uploads and email attachments, each one a copy on somebody else’s servers. Handing everyone the bucket keys is not an option, and a shared drive is a different kind of mess.

What s3nd does about it

Mount the transfer handler on a domain you own and pass an authorize function that checks a bearer token. Each person has a token, not a key. The CLI points at it with --remote, a browser uses the same four routes through the React hooks, and curl works too. Files land in your bucket under a code and expire on their own.

Worth watching

The things that are easy to get wrong.

Rate-limit the lookup

A code is a bearer token and forty bits is the whole secret. A rate limit on GET /:code is what makes guessing pointless.

raw: redirect for big files

With it, a download answers 302 with a presigned URL, so the bytes never transit your server twice. Without it they stream through, which is fine for small files and expensive for large ones.

doctor --remote as the smoke test

It round-trips a real transfer against the deployment and exits non-zero on failure. Put it in the deploy pipeline.

Packagess3nd@s3nd/cli

Put a file. Hand over the code.

Point it at the bucket you already pay for. Nothing to deploy, nothing to sign up for, nothing in the middle.

npm install -g @s3nd/clis3nd init --provider r2 --bucket drops3nd put ./anything.zip