Your CI already builds the artifact.
Then someone pastes it into Slack as a 40 MB attachment — or opens a throwaway Google Drive link that expires next week with no signal that anyone opened it.
If the person on the other end is not living in GitHub Releases, you need a handoff link: one URL, browser-readable, optional password/expiry, and a simple “was it opened?” check.
This post walks through a practical pattern:
- Build a ZIP (or PDF) in GitHub Actions
- Upload it with one authenticated
curl - Post the share URL back on the PR (or into Slack)
I use TiniDrop for the share step because that is what we built for this exact loop — but the workflow shape is the same idea wherever you land the file: artifact in CI → link out → humans click a URL, not a binary.
When this beats GitHub Releases (and email)
Use a share link when:
- The recipient is a client, investor, or teammate who is not cloning repos
- You are shipping a finished ZIP / PDF / HTML preview, not ongoing source
- You want a lightweight “did they open it?” signal after the PR merges
- You might want a password or expiry on that one URL
Keep Releases for versioned open-source downloads. Use a link for the human handoff.
What you need
- A GitHub repo with Actions enabled
- A ZIP (or PDF) produced by your job —
dist.zip,site.zip,deck.pdf, etc. - A TiniDrop account on Solo or Pro (API keys live under Dashboard → Settings → API keys)
- An API key stored as a GitHub Actions secret — never in the repo
Generate the key once in the dashboard. Name it something like github-actions-ci. Copy it only at creation time.
Add a repository secret:
- Name:
TINIDROP_API_KEY - Value: the key (starts with
td_if you created it recently)
Minimal upload from your laptop (sanity check)
Before wiring Actions, prove the API from your machine:
curl -sS -X POST https://tinidrop.com/api/upload \ -H "x-api-key: $TINIDROP_API_KEY" \ -F "file=@dist.zip" A successful response includes a shareable url (and a slug). Shape:
{ "slug": "a8k2mx", "url": "https://tinidrop.com/view/a8k2mx", "original_name": "dist.zip", "size_bytes": 1843200 } Open that url in a private window. If it loads, you are ready for CI.
Note: API upload is for Solo / Pro. Free-plan uploads go through the browser (bot check). Put the key in CI secrets and rotate it if it ever leaks — same hygiene as any deploy token.
GitHub Actions: upload after build
Here is a job that builds, zips, uploads, and prints the link.
name: Share build as link on: workflow_dispatch: push: branches: [main] jobs: share: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build run: | # replace with your real build mkdir -p dist echo "<h1>Preview</h1>" > dist/index.html - name: Zip artifact run: | cd dist && zip -r ../dist.zip . - name: Upload to TiniDrop id: upload env: TINIDROP_API_KEY: ${{ secrets.TINIDROP_API_KEY }} run: | set -euo pipefail RESP=$(curl -sS -X POST https://tinidrop.com/api/upload \ -H "x-api-key: ${TINIDROP_API_KEY}" \ -F "file=@dist.zip") echo "$RESP" | tee upload.json URL=$(echo "$RESP" | jq -r '.url') if [ -z "$URL" ] || [ "$URL" = "null" ]; then echo "Upload failed — check API key and plan" exit 1 fi echo "url=$URL" >> "$GITHUB_OUTPUT" echo "Share URL: $URL" - name: Summary run: | echo "### Build ready" >> "$GITHUB_STEP_SUMMARY" echo "[Open share link](${{ steps.upload.outputs.url }})" >> "$GITHUB_STEP_SUMMARY" What this gives you:
- A green check on
main(or manual dispatch) - A clickable link in the Actions job summary
- No attachment bouncing through email size limits
Bonus: comment the link on the pull request
Useful when reviewers are not watching the Actions tab:
name: PR preview link on: pull_request: types: [opened, synchronize] jobs: preview: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v4 - name: Build + zip run: | mkdir -p dist && cp -R public/* dist/ || true # your build here… cd dist && zip -r ../preview.zip . - name: Upload id: upload env: TINIDROP_API_KEY: ${{ secrets.TINIDROP_API_KEY }} run: | RESP=$(curl -sS -X POST https://tinidrop.com/api/upload \ -H "x-api-key: ${TINIDROP_API_KEY}" \ -F "file=@preview.zip") URL=$(echo "$RESP" | jq -r '.url') echo "url=$URL" >> "$GITHUB_OUTPUT" - name: Comment on PR uses: actions/github-script@v7 with: script: | const url = '${{ steps.upload.outputs.url }}'; github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `### Preview ready\n\nOpen the share link (view in browser):\n${url}` }); Reviewers click once. They do not need a GitHub package download or a local unzip ritual.
PDF decks and client ZIPs
Same pattern, different artifact:
- Pitch / proposal PDF — export in CI or commit the latest PDF, upload, send the link before the call
- Client deliverable ZIP — zip the folder after
npm run build, upload, paste into email - HTML prototype — zip a folder that contains
index.html; the share link opens in the browser
After you send it, check whether it was opened, then follow up with a specific next step — not “just checking if you got this.”
Hardening checklist
- [ ] Secret is
TINIDROP_API_KEYin GitHub — never in YAML - [ ] Key is Solo/Pro; rotate if a fork PR could have read it (prefer environment secrets with approval for forks)
- [ ] Fail the job if
.urlis missing — do not silently “succeed” with a broken handoff - [ ] For sensitive rounds, set password and expiry on the link from the dashboard after upload
- [ ] Do not treat a share link as a substitute for Git — source stays in the repo
FAQ
Do recipients need an account?
No. They open the link in a browser.
Is this a replacement for GitHub Releases?
No. Releases win for public versioned downloads. This wins for fast, private-ish handoffs to non-Git audiences.
What file types?
PDF, ZIP, HTML/HTM, and common images. Keep the ZIP for small static previews; keep Git for real collaboration.
Where do I start without CI?
tinidrop.com → drop a file → copy the link. Wire Actions once the manual loop feels right.
I am Santosh, co-founder of TiniDrop (Würzburg). If your team still pastes build ZIPs into Slack as attachments, say what you ship in the comments — CI preview, client ZIP, or deck — and we can compare notes on the handoff.

