DEV Community

Cover image for Replace email attachments in CI — upload a ZIP from GitHub Actions and get a shareable link
TiniDrop for TiniDrop

Posted on Edited on

Replace email attachments in CI — upload a ZIP from GitHub Actions and get a shareable link

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:

  1. Build a ZIP (or PDF) in GitHub Actions
  2. Upload it with one authenticated curl
  3. 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"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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}`
            });
Enter fullscreen mode Exit fullscreen mode

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_KEY in 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 .url is 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.

Top comments (0)