Postage is an email-native footage festival. You send one public video URL to the booth, the tape is ingested and screened, and a citation comes back in the same thread. It uses Mux Robots for auto-captioning and parallel evaluations, and Resend Inbound for receiving submissions and delivering threaded replies.
The diagram below illustrates the path from a submitted link to a threaded reply.
%%{
init: {
"themeCSS": ".edgeLabel .labelBkg { background: transparent !important; } .edgeLabel span:not(:empty) { padding: 5px 12px; border-radius: 6px; display: inline-block; }",
"flowchart": {
"padding": 20,
"rankSpacing": 60,
"nodeSpacing": 60,
"subGraphTitleMargin": {
"top": 12,
"bottom": 16
}
}
}
}%%
flowchart TD
User(["User"])
Notice(["Notice"])
Failed(["Failed"])
Citation(["Citation"])
%% Vertical restraint for terminations
Notice ~~~ Failed
Failed ~~~ Citation
ResendSvc["Resend\nInbound · Templates · Thread"]
MuxSvc["Mux\nVideo · Captions · Robots"]
subgraph Registry["Registry"]
RHook["/api/webhooks/resend\nSvix verified"]
Fetch["Receiving API\nRead body & HTML"]
Parse{"Public video\nURL?"}
Asset["Create asset"]
end
subgraph Booth["Booth"]
MHook["/api/webhooks/mux\nSignature verified"]
Caps["Generate captions\nlanguage: auto"]
Screen["ask-questions\nfestival rubric"]
Moments["find-key-moments\nshots · novelty"]
Note["ask-questions\nscoped note"]
end
User -- "Email the desk" --> ResendSvc
%% Invisible links stretch the dashed edges so labels float perfectly in the middle
ResendSvc ~~~~ RHook
ResendSvc -. "email.received" .-> RHook
RHook --> Fetch --> Parse
Parse -- no --> Notice
Parse -- yes --> Asset
Asset -- error --> Notice
Asset --> MuxSvc
%% Stretch Mux webhook edge symmetrically
MuxSvc ~~~~ MHook
MuxSvc -. "webhook events" .-> MHook
MHook --> Caps
Caps --> Screen
Caps --> Moments
Screen --> Note
Moments --> Note
Screen -- error --> Failed
Note --> Citation
Follow these steps to set up and run Postage on your local machine.
This app requires Node.js 24.11.1+ and pnpm. You also need accounts on Mux, Resend, and ngrok.
git clone https://github.com/nabarvn/postage.git
cd postagepnpm installCreate a local environment file by copying the example:
cp .env.example .envImportant
Ensure you populate the variables with your respective API keys and configuration values before proceeding.
Mux and Resend both need a stable HTTPS endpoint to deliver webhooks.
-
Install ngrok and sign in.
-
Grab your free dev domain from the ngrok dashboard (e.g.
your-subdomain.ngrok-free.app). -
Start the stable public tunnel in a separate terminal:
ngrok http 3000 --url https://your-subdomain.ngrok-free.app
This creates your two webhook routes:
https://your-subdomain.ngrok-free.app/api/webhooks/muxhttps://your-subdomain.ngrok-free.app/api/webhooks/resend
Mux
- Confirm Robots is enabled in your dashboard, then create a new access token with Video and Robots permissions. Copy the Token ID and Secret to
MUX_TOKEN_IDandMUX_TOKEN_SECRETin.env. - Add a webhook pointing to your ngrok Mux URL. Copy the endpoint signing secret to
MUX_WEBHOOK_SECRET.
Resend
- Set
NEXT_PUBLIC_RESEND_RECEIVING_ADDRESSin.envto your*.resend.appreceiving address. - Create and publish your templates (Citation and Notice). Add the required string variables to Citation (
TITLE,CATEGORY,SYNOPSIS,AWARD,NOTE,VERDICT) and Notice (NOTICE). Set their respectiveRESEND_*_TEMPLATE_IDs in.env. - Add a custom domain and verify your DNS records. Set
RESEND_FROMin.env. Optionally, enable Receiving (requires an MX record) and setNEXT_PUBLIC_CUSTOM_RECEIVING_ADDRESSto accept submissions on your own branded email address. - Create an API key and copy it to
RESEND_API_KEYin.env. - Add a webhook listening for the
email.receivedevent at your ngrok Resend URL. Copy the signing secret toRESEND_WEBHOOK_SECRET.
Start the Next.js dev server once your ngrok tunnel is active in a separate terminal:
pnpm devNote
Open http://localhost:3000 in your browser to access the local app.
Send an email containing a public video URL to your configured receiving address to test the screening pipeline.
- Language: TypeScript
- Framework: Next.js
- Styling: Tailwind CSS
- Video Processing: Mux
- Email Delivery: Resend
- Mux generates captions before doing anything else. Once ready, the general screening and key moments tasks run at the same time. The note task runs last, focused specifically on the best moment found in the video.
- The Resend webhook only provides metadata, so the app fetches the actual email body right after. Replies stay in the correct thread using an
In-Reply-Toheader and use a unique key to prevent duplicate sends. - Because key moments use milliseconds and the note scope uses seconds, the end time is pulled back by one millisecond. This ensures the reading window never exceeds the total video duration.
- Only public video links work. Google Drive and Dropbox links are rewritten into direct download URLs so Mux can process them. Internal network links and URLs buried in old email replies are ignored.
- If key moments detection fails, the note evaluation runs unscoped; if the note task fails, the citation is generated directly from screening outputs; and processed assets in Mux are pruned automatically to keep only the five most recent submissions.