Send links and text from your Android phone to a Mac menubar app — from any network (home Wi-Fi, office, mobile data). No accounts, no cloud database, no pairing, no same-Wi-Fi requirement.
Delivery rides on an Upstash Redis Stream, but everything is end-to-end encrypted (AES-256-GCM). A single shared secret — baked into both apps at build time — is the only thing that ties your phone to your Mac. The relay only ever stores ciphertext.
Because the relay stores messages (30-day retention) rather than broadcasting them, your Mac doesn't have to be awake. Share something with the laptop shut; it's waiting there the next time the app polls.
stash-mac: Electron menubar app. Polls the relay stream, decrypts incoming links, and lists them in a menubar popover with title, description, and favicon.stash-android: Kotlin app. Appears in the Android share sheet, encrypts what you share, publishes it to the relay, and queues on disk if you're offline.
Both apps must be built with the exact same secret. Generate one:
openssl rand -base64 24Then write that identical value into both files (both are gitignored — the secret never lands in git):
Create a free Redis database at console.upstash.com and copy its REST URL and REST token. This is the relay; both apps talk to it directly over HTTPS.
stash-mac/stash.secret.json
{
"sharedSecret": "PASTE_THE_SAME_SECRET_HERE",
"upstashRedisRestUrl": "https://your-db.upstash.io",
"upstashRedisRestToken": "PASTE_THE_REST_TOKEN"
}stash-android/secrets.properties
STASH_SHARED_SECRET=PASTE_THE_SAME_SECRET_HERE
UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
UPSTASH_REDIS_REST_TOKEN=PASTE_THE_REST_TOKENIf the shared secrets differ by even one character, the apps derive different relay streams and encryption keys, and nothing will arrive. This is the #1 thing to double-check.
Both apps also accept STASH_SHARED_SECRET, UPSTASH_REDIS_REST_URL, and UPSTASH_REDIS_REST_TOKEN as environment variables, which take priority over the files.
Requires Node.js + pnpm.
cd stash-mac
pnpm install
pnpm run dist
open release/stash-0.1.0-arm64.dmgIn the DMG window, drag stash into Applications, then launch it. It runs in the menu bar only: no dock icon, no window at all. Everything in stash.secret.json (shared secret and relay credentials) is bundled into the app automatically, so the installed app needs no configuration.
Because the build is unsigned, macOS Gatekeeper blocks it on first launch. Either:
- Right-click the app in Applications → Open → Open, or
- Clear the quarantine flag:
xattr -dr com.apple.quarantine /Applications/stash.app
Open stash-android in Android Studio, connect your phone (USB debugging on), pick it in the device picker, and click Run. Android Studio handles the JDK and build; the secret and relay credentials from secrets.properties are compiled in automatically.
From any app on your phone, tap Share → stash. The link (or text) appears in the Mac menubar menu on its next poll, from any network.
- Sent to Mac → it's on the relay stream. Delivery is guaranteed from here: the entry is stored for 30 days and the Mac reads it whenever it next runs.
- Saved, will send when back online → your phone had no connection. It's on disk and goes out as soon as connectivity returns (or on your next share).
Click the menubar icon to open the popover. Each card shows the favicon, title, description, and source. Click a card to copy the link — this also removes it from the list. Use the open button to send it to your browser instead, the trash icon to clear everything, and the gear for settings.
Polling is every 30 seconds while you're using the Mac, backing off to 5 minutes once it's been idle for a while. Waking, unlocking, or just opening the popover forces an immediate check, so nothing sits waiting when you actually go looking for it.
Pick a new value, update both stash.secret.json and secrets.properties, then rebuild and reinstall both apps. Old in-flight messages keyed to the previous secret will simply be ignored (they sit in the old streams until the 30-day trim removes them).
The same applies to the Upstash token: rotate it in the console, update both files, rebuild both apps. Note that both the secret and the token are extractable from the APK and the DMG, so treat build artifacts as sensitive.
Android release APK (configure signing in Android Studio or a Gradle signing config first):
cd stash-android
./gradlew assembleReleaseMac builds are currently unsigned/un-notarized (fine for personal use). For a signed/notarized build you'd need an Apple Developer ID; the packaging metadata lives in stash-mac/package.json under the build field.
- Nothing arrives on the Mac. Almost always a secret mismatch — verify
stash.secret.jsonandsecrets.propertieshold byte-for-byte identical values, then rebuild both. The Mac logsshared secret configured = trueandrelay credentials configured = trueon startup; if either saysfalse(or it warns about a PLACEHOLDER), that file wasn't found or is missing a field. - The popover header says "Offline". The Mac can't reach the relay: check the Upstash URL/token, and that the database still exists in the console. Note the phone will still say "Sent to Mac" in this case, because publishing succeeded; the phone has no way to know the Mac is unreachable.
- A link takes up to 30 seconds to appear. That's the poll interval, by design (5 minutes if the Mac has been idle). Opening the popover, waking, or unlocking forces an immediate poll.
- No menubar icon after install. Reinstall from the latest
release/stash-0.1.0-arm64.dmg(quit the old copy first withkillall stash), then replace the app in Applications. - App shows in the dock. It shouldn't — the packaged app sets
LSUIElement, making it menubar-only. If you see a dock icon, you're running an older build; rebuild and reinstall. - Gatekeeper won't open it. See the
xattrcommand in Step 2. - Titles are missing. Some sites block metadata fetches; the hostname fallback is expected.
See CONTEXT.md for the full design: the encrypted wire protocol, how the secret derives the relay stream name and key, why delivery needs no acknowledgements, and per-app implementation notes.