@mdemb/colis
Send from a terminal. Follow the answer.
colis envoyer sends a file and prints its code; colis statut says whether your client opened it, approved it or asked for changes. The commands are in French, and put, get, rm and doctor still work as aliases.
$ colis envoyer ./maquette-v2.pdf
maquette-v2.pdf · 1.8 MB · expires in 1 day
K7QP2M4X
$ CODE=$(colis envoyer ./export.zip) # the code on stdout, the rest on stderr
$ tar cz ./site | colis envoyer - --name site.tar.gz
$ colis statut K7QP2M4X
ouvert
envoyé 2026-09-22 10:00
ouvert 2026-09-22 11:00
answer not yet
$ colis statut K7QP2M4X | head -1 # the state alone, for a script
ouvert
$ colis envoyer ./maquette-v3.pdf --remplace K7QP2M4X # changes asked: same code, same link
maquette-v3.pdf · 1.9 MB · expires in 1 day
Version 2 envoyée sur K7QP2M4X
K7QP2M4XInstall
One npm command, nothing to configure.
`npm i -g @mdemb/colis` installs the `colis` command. Node 20 or later. With nothing configured, `colis envoyer` sends to the public colis service — files up to 100 MB, kept 24 hours at most, 20 uploads an hour from one address — and `colis statut` follows the delivery. For your own storage, configure a bucket or point it at your own delivery page.
npm i -g @mdemb/colis
# nothing to configure: the public colis service
colis envoyer ./maquette.pdf
colis statut <CODE>envoyer and statut
The code on stdout, the answer on demand.
Against your delivery page, every parcel gets a receipt. statut prints the state on its first line, then each step with its time, and the client’s comment last.
statut asks a delivery page: the receipt is kept there, not in the bucket. With nothing configured, that is the public service. With a bucket and no --remote, statut says so, says whether the file is still there, and exits non-zero. --json gives the whole receipt, and a parcel with a password wants it too, with --token.
$ colis recevoir k7qp-2m4x # lower case, a dash: still found
Wrote /home/you/maquette-v2.pdf · 1.8 MB
$ colis recevoir K7QP2M4X -o - | less # or to stdout
$ colis annuler K7QP2M4X # burn it before it expires
Burned K7QP2M4Xinit and verifier
The command worth running first.
Pointed at a server, verifier makes a real round trip. Pointed at a bucket, it performs the operations colis needs and reports what happened, lifecycle rule included. The probe is deleted before it returns.
$ colis init --provider remote
Wrote /home/you/client-acme/colis.config.json
Point "remote" at your deployment of the transfer routes.
Put its token in .env as COLIS_TOKEN. This machine needs no S3 credentials at all.
Run `colis verifier` — it performs the operations colis needs and reports what happened.$ colis verifier
Using /home/you/client-acme/colis.config.json
✓ Server: https://livraison.example.com/api/transfers answered
✓ Create, read, delete: round-tripped code 8WTXQC8R
Ready to store transfers.$ colis -p r2 verifier
Using /home/you/client-acme/colis.config.json (profile "r2")
✓ Configuration: bucket "livraisons", region "auto"
✓ Credentials: resolved, key ends in 1a2b
✓ Bucket reachable: HeadBucket succeeded
✓ Write, read, delete: round-tripped a probe object
! Expiry cleanup: no enabled expiration rule
→ Add an S3 lifecycle rule that expires objects under this bucket after a day or
two. Without it, expired transfers stay stored and billed.
1 check(s) failed.verifier exits non-zero when a check fails, so it works as a deployment smoke test. init --provider writes a starter for aws, r2, minio, scaleway, wasabi or remote, with ${VAR} references rather than secrets.
Configuration
Committable settings, uncommittable keys.
${VAR} is read from the environment, and envFile names a file to load first without overwriting what the shell already set. Profiles hold several setups in one file. A flag beats a variable, which beats the file.
{
"envFile": ".env",
"profiles": {
"livraison": {
"remote": "https://livraison.example.com/api/transfers",
"token": "${COLIS_TOKEN}"
},
"r2": {
"bucket": "livraisons",
"region": "auto",
"endpoint": "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com",
"expiresIn": "7d",
"credentials": { "accessKeyId": "${R2_ACCESS_KEY_ID}", "secretAccessKey": "${R2_SECRET_ACCESS_KEY}" }
},
"local": { "bucket": "livraisons", "endpoint": "http://localhost:9000" }
}
}$ colis -p livraison envoyer ./maquette-v2.pdf # through your delivery page
$ colis -p livraison statut K7QP2M4X
$ colis -p local verifier # against a MinIO container
$ colis config # which value won, and where it came fromThe file is looked for from the working directory upwards, then in ~/.config/colis/config.json. colis config prints which value won and where it came from; it masks the access key id and never prints the secret or the token.
Reference
Seven commands, a dozen options.
Commands
- envoyer <file>
- Store a file and print the code to carry. "-" reads stdin
- recevoir <code>
- Fetch what a code points at, to the stored filename, -o, or stdout
- statut <code>
- Delivery tracking: sent, opened, approved or changes. Needs --remote
- annuler <code>
- Burn a code
- verifier
- Check this setup can actually store transfers. Exits non-zero when a check fails
- init
- Write a starter colis.config.json for aws, r2, minio, scaleway, wasabi or remote
- config
- Print the resolved configuration, and where each value came from
- put, get, rm, doctor
- The English names, kept as aliases
Options
- -c, --config
- Configuration file to read
- -p, --profile
- Profile to use inside it
- --env-file
- Read KEY=value pairs from this file first
- --bucket, --prefix, --region, --endpoint
- Bucket settings, overriding the file and the environment
- --expires-in
- 3600, 30m, 24h, 7d, or never
- --remote, --token
- Talk to a colis server instead of S3 directly, with its password
- --name
- Filename to store the transfer under
- -o, --output
- Where recevoir writes. "-" is stdout
- --json
- Machine-readable output, for scripts and for verifier in CI
- --provider, --force
- Which starter init writes, and whether it may overwrite
Configuration otherwise comes from environment variables: COLIS_REMOTE, COLIS_TOKEN, COLIS_BUCKET, COLIS_ENDPOINT and the usual AWS credentials. The argument parser is node:util’s parseArgs, so there is nothing else to install.
Send it. They sign off. You know.
Your storage, a delivery page under your name, a webhook at every step. Nothing hosted by someone else.