Goal

Build and ship a self-hosted file transfer app that makes sending files from a Mac to an Android phone feel instant and native — no third-party cloud, no size limits, just tap and it’s there. Eventually open-source and available on the Play Store.


Overview

SendIT is a two-part system: a Mac menubar app (Python server, py2app bundle) and an Android app (Capacitor + Java). Files are sent from the Mac via a web interface or Finder Quick Action, and the phone receives a push notification, taps it, and gets a download prompt — no hunting, no copying links.

The server runs locally on the MacMini and is accessible over the local network or remotely via Tailscale. The LMBP runs in client_only mode, connecting to the MacMini server at 100.83.193.91:8765.

GitHub: VLCHALWELL/sendit-app (private) — current release v1.0.33

What makes it unique — no other tool has all of this:

  • Finder Quick Action (right-click → Send to Phone)
  • File dashboard with live WebSocket updates
  • FCM push notifications (works in background, Samsung-compatible)
  • Cross-device clipboard sync (Mac ↔ Phone, on-demand)
  • Android share sheet → send text/URLs directly to Mac
  • QR code pairing — scan to connect any device instantly
  • Self-hosted (no cloud middleman)
  • Off-network via Tailscale

Tech Stack

LayerTech
Mac serverPython 3.9, py2app bundle, FastAPI/WebSocket
Android appCapacitor, Java, Firebase FCM
SyncWebSocket (live file list) + HTTP (upload/download)
NotificationsFirebase Cloud Messaging (FCM)
Off-networkTailscale
BuildGradle, build.sh, deploy_server.sh

Key paths on MacMini:

  • Source: _Tools/sendit/
  • App bundle: /Applications/SendIT.app
  • Server log: /tmp/sendit-server.log
  • Dev server: running directly via nohup python3 server.py
  • Android SDK / adb: ~/Library/Android/sdk/platform-tools/adb
  • Device serial (Fold7): RFCY61FPBWK

What’s Been Built ✅

Core Features

  • Mac menubar app with system tray icon and “Send to Phone…” action
  • File dashboard — drag & drop uploads, live file list via WebSocket
  • Finder Quick Action — right-click any file to send directly
  • Android app with share sheet support (onNewIntent + appStateChange)
  • Password-authenticated server
  • URL history (up to 5 saved server URLs) on setup screen
  • Single-use download tokens (/api/dl/{token}) for secure transfers
  • Multi-file send from Mac and mobile
  • Thumbnail previews for images (with ngrok header fix)
  • “Show [filename] in Finder” menu item as notification click workaround

Clipboard Sync (shipped 2026-04-17)

On-demand clipboard sharing between Mac and phone, triggered by button:

  • Mac → Phone: “Copy to Phone” menubar item reads macOS clipboard via pbpaste, POSTs to /api/clipboard with source: mac; FCM fires a notification on Fold7; Android app shows a clipboard section with a “Paste to Device” button
  • Phone → Mac: ”📋 → Mac” button in the Android app opens a paste sheet (long-press → paste → Send); server broadcasts clipboard_received event; all connected Macs write the text via pbcopy and show a notification
  • Both Macs (MacMini + LMBP) receive simultaneously — no device targeting needed
  • Paste sheet workaround avoids need for @capacitor/clipboard plugin (navigator.clipboard.readText() fails in Capacitor WebView)

Android Share Target (shipped 2026-04-17)

SendIT now appears in the Android share sheet for text and URLs:

  • ShareTargetPlugin.java updated to extract EXTRA_TEXT + EXTRA_SUBJECT from share intents (in addition to existing file handling)
  • JS checkPendingShares() detects text shares and pre-fills the paste sheet modal
  • User reviews, taps Send → text arrives on Mac clipboard via the clipboard sync endpoint
  • File sharing flow unchanged — purely additive change

QR Code Pairing (shipped 2026-04-17)

Connect a new device without typing a URL or password:

  • Mac: menubar “Show Pairing QR” opens /qr in the browser — shows tabbed QR codes for all available addresses (Local Wi-Fi, Tailscale, ngrok); switch tabs to show the right one for the current network
  • Android: ”📷 Scan QR instead” button on setup screen uses rear camera + bundled jsQR to decode — auto-fills URL + password and connects immediately
  • QR encodes {"url": "...", "password": "..."} — works across all network types
  • jsQR bundled locally (not CDN) for reliable offline loading in Capacitor WebView

Push Notifications (Samsung fix)

Samsung Galaxy Fold 7 was suppressing Firebase’s default background notification display. Fixed with a custom SendITMessagingService.java:

  • Extends FirebaseMessagingService, displays notification directly via NotificationCompat
  • Server sends data-only FCM messages (no notification: payload) so onMessageReceived always fires
  • Service registered in AndroidManifest.xml with android:priority="1"
  • Plugin’s MessagingService suppressed via tools:node="replace"
  • FCM fires on every upload (not just “Send to Phone” — all paths: drag & drop, web, quick share)
  • Stale FCM token cleanup handled (multiple error types)

Infrastructure

  • install.sh — unified installer (server + client options)
  • README.md — full open source README with competitor matrix
  • GitHub repo + SSH key + gh CLI set up on MacMini
  • LMBP configured as client_only via Tailscale
  • deploy_server.sh — server-only deploys without full rebuild
  • JDK 21 installed via Homebrew (temurin@21)

Version History

VersionWhat changed
v1.0.1Fixed adaptive icon (rocket); dark navy background
v1.0.2Fixed share sheet: onNewIntent() + appStateChange listener
v1.0.3File list retry on slow connections; refresh after upload
v1.0.4URL history dropdown on setup screen (up to 5 URLs)
v1.0.5Password pre-filled on setup (was blank → 401 on file list)
v1.0.6File list pushed over WebSocket on connect
v1.0.7Guard ws.onmessage against non-JSON frames
v1.0.8Fixed broken thumbnails on ngrok (skip-browser-warning header)
v1.0.13Notifications working (last known good)
v1.0.17FCM Samsung fix + data-only messages + stale token cleanup
v1.0.25-debugJava onResume() tap handling (partially working — see below)
v1.0.26–v1.0.29Clipboard sync (Mac ↔ Phone) + FCM notification on clipboard send
v1.0.30Android share target — share text/URLs from any app directly to Mac
v1.0.31–v1.0.33QR code pairing — tabbed Local/Tailscale/ngrok codes; Scan QR on setup screen

Roadmap / What’s Left 📋

Immediate

  • Rebuild .app bundle — bake all recent changes into /Applications/SendIT.app via bash build.sh
  • Push v1.0.33 to GitHub
  • LMBP SSH access set up — MacMini can now push menubar updates directly (lchalwell@100.116.60.79)

Play Store

  • Upload screenshots
  • Generate signed AAB
  • Fill out content rating
  • Submit for review

Future / Nice-to-have

  • Clipboard history — last N items stored on the server, browsable from the Android app or dashboard
  • Clipboard image support — currently text-only; extend to send images via clipboard (Base64 or temp file)
  • Quick text snippets — save frequently-sent snippets (URLs, addresses, etc.) to the dashboard for one-tap send
  • Per-device targeting — currently clipboard broadcasts to all connected clients; allow sending to a specific device
  • Auto-start server on Mac login — add as a LaunchAgent so the dev server doesn’t need a manual start
  • Progress bar for large file transfers — stream progress via WebSocket
  • File expiry / auto-cleanup — auto-delete files from server after N days or on download
  • iOS PWA — Android-native app exists; iOS could work as a PWA for dashboard access + clipboard sync
  • QR code pairing — scan a QR from the dashboard to set up a new device without typing URL/password
  • Make GitHub repo public + open source release

Competitor Landscape

Full breakdown in SendIT Competitor Comparison.

AppSelf-hostedOff-networkPush notifFinder QADashboard
SendIT✅ (Tailscale)
Blip
LocalSend
KDE Connect
PairDrop
Quick Share

Note: Blip breaks on MacMini (Unix socket path length bug with external home folder) but works on LMBP.


Key Debugging Notes

  • Old server still running: pkill -f "python3 server.py" doesn’t always work. Use: lsof -nP -i :8765 | awk '/LISTEN/{print $2}' | xargs kill -9
  • Deploy server changes: Never copy .py alone. Use deploy_server.sh — it recompiles server.pyc inside lib/python39.zip in the app bundle
  • Full rebuild: bash build.sh then manually copy _cffi_backend.so after
  • Static assets: Always sync before Android build: cp -r static/. android/app/src/main/assets/public/
  • FCM silently fails: Wrap _send_fcm with prints; the outer except: pass hides all errors
  • Signing conflict: Uninstall APK before switching debug ↔ release signing configs

Notes