# Fonctionnement

> Un colis est un fichier dans votre bucket sous un code de huit caractères, avec une durée de vie et un accusé de réception à côté : envoyé, ouvert, validé ou à corriger. La page de retrait, l’accusé, les gros fichiers et le protocole en dessous.

Canonical: https://colis-site.vercel.app/how-it-works · Markdown: https://colis-site.vercel.app/how-it-works.md · English: https://colis-site.vercel.app/en/how-it-works

colis est volontairement petit. Cette page en fait le tour : ce qu’est un colis, ce que note l’accusé de réception, comment voyagent les gros fichiers, et le protocole que partagent la page, la CLI et les automatisations.

**Vous. Votre bucket. Votre client. Rien d’autre.**

- **Vous envoyez** — Le fichier arrive dans votre bucket en un seul objet, sous un code tout neuf de huit caractères, avec son expiration inscrite sur l’objet et un accusé de réception à côté.
- **Le code change de mains** — Un lien dans un e-mail, huit caractères dictés au téléphone, un QR code sur un écran. Aucun compte, d’un côté comme de l’autre.
- **Votre client répond** — Il tape le code, fautes réparées, prévisualise le fichier et le valide. L’accusé le note, et le webhook part.

## Envoyé, ouvert, validé ou à corriger.
Chaque colis a son accusé de réception, rangé à côté de lui dans votre bucket : quatre états, chacun avec son heure, et une réponse qui ne change plus.

- **envoyé** — Écrit à la création du colis, avec l’empreinte de votre jeton d’expéditeur. Ce jeton permet de distinguer vos propres visites de celles de votre client.
- **ouvert** — La première fois que quelqu’un d’autre que vous ouvre la page de retrait, télécharge le fichier ou répond. La première fois seulement.
- **validé** — Le client a cliqué sur valider. Définitif : une seconde réponse est refusée.
- **à corriger** — Le client demande des corrections et dit lesquelles, en 1 000 caractères au plus. Définitif aussi : la version suivante est un nouveau colis.

**l’accusé, depuis un terminal**
```sh
$ colis statut K7QP2M4X
à corriger
envoyé      2026-09-22 10:00
ouvert      2026-09-22 11:30
à corriger  2026-09-22 12:45
comment     Le logo en SVG, et le fond plus clair.

$ colis statut K7QP2M4X --json    # the whole receipt, for a script
```

L’accusé survit au fichier : il répond encore après qu’un téléchargement unique a supprimé le code, aussi longtemps que la plus longue durée de vie du déploiement. Votre page d’envoi le suit toute seule, `colis statut` l’affiche, et chaque changement est un webhook.

## Un objet, un code, une durée de vie.
Le fichier est stocké tel quel, avec son nom, son type et son expiration dans les métadonnées de l’objet. Tout ce que la page sait d’autre tient dans de petits enregistrements à côté, sous `meta/`, hors de portée des routes de transfert.

**un colis, tel qu’il arrive dans le bucket**
```text
drop/K7QP2M4X                 # the file
  Content-Type:         application/pdf
  Content-Disposition:  attachment; filename="maquette-v2.pdf"
  x-amz-meta-colis-kind:        file
  x-amz-meta-colis-expires-at:  2026-09-23T10:00:00.000Z
  Body:                 the bytes, untouched

drop/meta/K7QP2M4X            # note, device, one-time flag, password hash
drop/meta/K7QP2M4X.delivery   # sent, and the sender's token hash
drop/meta/K7QP2M4X.opened     # the first time the client opened it
drop/meta/K7QP2M4X.verdict    # approved, or changes and the comment
```

- **expiration à la lecture** — Un colis expiré n’est jamais servi, même si l’objet est encore dans le bucket. Un code expiré répond le même `NOT_FOUND` qu’un code qui n’a jamais existé.
- **règle de cycle de vie** — Supprimer l’objet, c’est le travail de votre bucket, via une règle de cycle de vie sur le préfixe. `colis verifier` contrôle qu’elle existe.
- **mot de passe** — Le mot de passe d’un colis est haché avec scrypt, avec son propre sel. Seule l’empreinte est stockée : personne ne peut le relire ni le réinitialiser.
- [La taille maximale d’un fichier](https://colis-docs.vercel.app/docs/limites)
- [La page de livraison, en détail](https://github.com/mamadouwhile/colis/blob/main/templates/drop/README.md)

## Du navigateur à votre bucket, sans détour.
Au-delà de `DROP_MAX_SIZE_MB`, rien ne passe par la fonction. Le serveur signe des URL et assemble ; le navigateur envoie les morceaux lui-même.

```text
POST   /api/transfers/uploads                 # a fresh code, a multipart upload
       → { code, uploadId, partSize, partCount }   # parts of 8 MiB
POST   /api/transfers/uploads/:code/parts     # presigned PUT URLs, 50 at most
PUT    <the bucket>                           # the browser, 4 parts at a time
POST   /api/transfers/uploads/:code/complete  # assembled, checked, then 201
DELETE /api/transfers/uploads/:code           # abandoned
```

Les morceaux font 8 Mio, davantage quand un fichier en demanderait plus de 10 000. Le navigateur en envoie quatre à la fois et retente trois fois un morceau qui échoue.

Il se souvient aussi de l’envoi. Redéposez le même fichier après une coupure ou un onglet fermé : il demande au bucket quels morceaux sont déjà là et envoie le reste.

Le bucket a besoin d’une règle CORS qui autorise `PUT` depuis votre page et expose `ETag`. Sans elle, les petits fichiers fonctionnent toujours, et un gros échoue avec un message qui dit pourquoi.

## Quarante bits qui survivent à un appel.
Le code, c’est ce que manipule votre client. Il s’affiche sur votre écran et il le tape sur le sien : tout en découle.

- **Base32 de Crockford** — Ni `I`, ni `L`, ni `O`, ni `U`. Les trois premiers se confondent à la lecture ; retirer le quatrième évite qu’un code aléatoire forme un mot malheureux.
- **Réparé au retour** — Séparateurs retirés, casse unifiée, `O` lu comme zéro. La réparation se fait dans le navigateur, avant toute requête, et ce que le client a tapé reste tel quel.
- **Réservé par écriture conditionnelle** — Le serveur choisit le code et écrit avec `ifAbsent` : une collision échoue franchement et réessaie avec un nouveau code au lieu d’écraser le colis de quelqu’un d’autre.
- [Les codes](https://colis-docs.vercel.app/docs/protocole#les-codes)
- [Longueur, alphabet, et ce que chacun coûte](https://colis-docs.vercel.app/docs/configuration#la-forme-des-codes)

## Quatre routes, un format d’erreur. Écrits noir sur blanc.
Un navigateur ne peut pas détenir vos identifiants S3 : un serveur se place entre les deux. La page de livraison est ce serveur, et sa forme est un protocole, pas un détail d’implémentation.

**relatives à /api/transfers**
```text
POST   /                # create a transfer, get the code back
GET    /:code           # metadata
GET    /:code/raw       # the bytes, or a 302 to a presigned URL
DELETE /:code           # burn it

# what the delivery page adds
GET    /:code/status    # sent, opened, approved or changes
POST   /:code/opened    # the client opened it
POST   /:code/verdict   # { "decision": "approved" }, or "changes" and a comment

# every error, same shape
{ "error": { "code": "NOT_FOUND", "message": "Unknown or expired code." } }
```
**le client, dans un navigateur**
```ts
import { createTransferClient } from '@colis/protocol'

const transfers = createTransferClient({ baseUrl: '/api/transfers' })

const { code } = await transfers.createFile({ body: file, filename: file.name })
const meta = await transfers.read(typed)        // null when unknown or expired
const bytes = await transfers.readBytes(code)   // the file back
await transfers.remove(code)
```

La CLI pointée vers `--remote` parle les mêmes quatre routes : c’est pourquoi `colis envoyer` fonctionne avec votre page de livraison et un jeton, sans clés S3.

La page de livraison ajoute trois routes à elle pour l’accusé de réception. Un client qui n’en connaît aucune fonctionne quand même : ce qu’il crée est un colis comme les autres, avec les réglages par défaut du déploiement.

[Le protocole de transfert, route par route](https://colis-docs.vercel.app/docs/protocole)

## Une seule contrainte décide du découpage.
Un navigateur ne doit jamais se retrouver avec un client de stockage dans ses dépendances. Le paquet protocole est ce que les deux moitiés partagent.

- **@mdemb/colis** (Le binaire; core, protocol) — envoyer, recevoir, statut, annuler, verifier, init et config. Une seule implémentation, le client du protocole, branché sur fetch ou directement sur le handler.
- **@colis/core** (La primitive; aws-sdk, protocol) — Fichiers, enregistrements, écritures conditionnelles et handler de transfert. Le seul paquet qui détient des identifiants.
- **@colis/protocol** (Le contrat; nanoid) — Le format d’échange, un client fondé sur fetch, les codes, et la signature des webhooks : signWebhook et verifyWebhook.
- **@colis/react** (Les hooks; protocol, react (peer)) — Envoyer, recevoir, et un champ de code. Aucun chemin de votre bundle ne mène au SDK AWS.
- **n8n-nodes-colis** (Le nœud n8n; protocol, n8n-workflow (peer)) — Un déclencheur qui vérifie la signature, et un nœud d’action qui envoie, lit et supprime des colis.

- **Runtime serveur**: Node 20 ou plus, ce qu’exige le SDK AWS v3
- **Page de livraison**: Next.js, déployée sur Vercel depuis le monorepo
- **Paquets navigateur**: fetch, et rien d’autre
- **Sur npm**: Pas encore : construits depuis le dépôt
