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
| Layer | Tech |
|---|---|
| Mac server | Python 3.9, py2app bundle, FastAPI/WebSocket |
| Android app | Capacitor, Java, Firebase FCM |
| Sync | WebSocket (live file list) + HTTP (upload/download) |
| Notifications | Firebase Cloud Messaging (FCM) |
| Off-network | Tailscale |
| Build | Gradle, 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/clipboardwithsource: 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_receivedevent; all connected Macs write the text viapbcopyand show a notification - Both Macs (MacMini + LMBP) receive simultaneously — no device targeting needed
- Paste sheet workaround avoids need for
@capacitor/clipboardplugin (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.javaupdated to extractEXTRA_TEXT+EXTRA_SUBJECTfrom 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
/qrin 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
jsQRto decode — auto-fills URL + password and connects immediately - QR encodes
{"url": "...", "password": "..."}— works across all network types jsQRbundled 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 viaNotificationCompat - Server sends data-only FCM messages (no
notification:payload) soonMessageReceivedalways fires - Service registered in
AndroidManifest.xmlwithandroid:priority="1" - Plugin’s
MessagingServicesuppressed viatools: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 +
ghCLI set up on MacMini - LMBP configured as
client_onlyvia Tailscale deploy_server.sh— server-only deploys without full rebuild- JDK 21 installed via Homebrew (
temurin@21)
Version History
| Version | What changed |
|---|---|
| v1.0.1 | Fixed adaptive icon (rocket); dark navy background |
| v1.0.2 | Fixed share sheet: onNewIntent() + appStateChange listener |
| v1.0.3 | File list retry on slow connections; refresh after upload |
| v1.0.4 | URL history dropdown on setup screen (up to 5 URLs) |
| v1.0.5 | Password pre-filled on setup (was blank → 401 on file list) |
| v1.0.6 | File list pushed over WebSocket on connect |
| v1.0.7 | Guard ws.onmessage against non-JSON frames |
| v1.0.8 | Fixed broken thumbnails on ngrok (skip-browser-warning header) |
| v1.0.13 | Notifications working (last known good) |
| v1.0.17 | FCM Samsung fix + data-only messages + stale token cleanup |
| v1.0.25-debug | Java onResume() tap handling (partially working — see below) |
| v1.0.26–v1.0.29 | Clipboard sync (Mac ↔ Phone) + FCM notification on clipboard send |
| v1.0.30 | Android share target — share text/URLs from any app directly to Mac |
| v1.0.31–v1.0.33 | QR code pairing — tabbed Local/Tailscale/ngrok codes; Scan QR on setup screen |
Roadmap / What’s Left 📋
Immediate
- Rebuild
.appbundle — bake all recent changes into/Applications/SendIT.appviabash 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.
| App | Self-hosted | Off-network | Push notif | Finder QA | Dashboard |
|---|---|---|---|---|---|
| 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
.pyalone. Usedeploy_server.sh— it recompilesserver.pycinsidelib/python39.zipin the app bundle - Full rebuild:
bash build.shthen manually copy_cffi_backend.soafter - Static assets: Always sync before Android build:
cp -r static/. android/app/src/main/assets/public/ - FCM silently fails: Wrap
_send_fcmwith prints; the outerexcept: passhides all errors - Signing conflict: Uninstall APK before switching debug ↔ release signing configs