Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

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 ot

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 "[email protected]"

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 "[email protected]")
          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 "[email protected]")
          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_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.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.