For the chronic texter. You know who you are. The triple-text. The "just checking in!!!" The essay at 1:47 AM that somehow made you look needier than the last one. SMSWingman is the wingman you wish you'd had before you typed yourself out of getting laid.
It sits between you and your texts: pulls threads in, drafts replies that don't scream anxious attachment, lets you tweak them, and sends. Built for dating-game SMS — including PUA-style flows out of the box — without turning you into a spam cannon.
Honest pitch: this is a hack. Real carrier SMS APIs in the US are a compliance maze (10DLC, consent, carriers, money). Access is annoying on purpose. So this project scrapes a consumer web inbox we call GV instead. It works. If you're technical, you can run it for practically nothing. Messages are not true real-time (though you can bolt on notifications). Sync / send / receive take a beat, and when GV's website changes, things can break. Help fix it when they do.
Personal, one-to-one use only. Not for spam, mass outreach, or blasting strangers. Consent and local law still apply. For real business SMS, use a proper provider — this is the cheap workaround for humans who text humans.
- Chronic texters who turn chemistry into a support-group chat
- People who want better SMS game without hiring a coach for every reply
- Technically inclined folks who want a dating SMS cockpit for nearly free
- Anyone who already has a GV number and an OpenAI-compatible LLM key
| Feature | Why you care |
|---|---|
| Three reply drafts | Pick the chill one, not the "hey did u get my last 4 msgs" one |
| System-wide chatbot personality | Teach it you: age, work, interests, vibe, hard rules |
| PUA-ready defaults | Tuned for dating SMS; change the system prompt if that's not your scene |
| Per-target sculpting | Tone, style, length, "push for date," custom instructions per person |
| Per-target memory | Remembers each conversation; auto-managed; you can edit it |
| Response delay | Optional wait before send so you don't look like you live in their inbox |
| Pull / sync / send via GV | No official SMS API required — browser automation does the clicking |
| Local SQLite + web UI | Your threads and drafts on your box, behind a simple login |
| CLI helpers | npm run login, npm run check, send/read via the same GV session |
- Someone texts you (on your phone / GV).
- You open SMSWingman → Pull messages (or sync that person).
- New inbound lands; the LLM returns three styled drafts.
- Edit the one that sounds like a person you'd want to date → Send.
- Optional delay so you're not instantaneous.
- Long threads get compacted into an editable memory summary so the model stays sharp.
You stay in control. The bot suggests. You hit send.
- Not real-time. Syncing and sending scrape a web UI. Expect seconds to tens of seconds, not Instant Messenger magic. There are ways to get notified faster (watchers, hooks, your own alerts) — community welcome.
- It can break. GV's site changes. Selectors die. PRs that fix scrapes are love.
- Hardest steps: standing up a Linux VPS + logging into GV remotely (VNC). After that, it's a breeze.
- It's still a hack. Cheap and clever ≠ carrier-grade. Treat it like a power tool, not a bank.
A small VPS, Node, Chrome, an OpenAI-compatible API key, and an evening of setup. No SMS aggregator bill. No 10DLC saga. Just you, a VM, and slightly better texts.
If this thing helps you get laid: show some love (tip / donate / hire me — details below). I built it, I need work, and paid installs keep the lights on.
Don't want to fight Xvfb and VNC? Hire me.
Nick Nguyen · Internet Technology Services · Dana Point / Orange County, CA
- Website: internettechnologyservices.com
- Email: Its@Internettechnologyservices.com
- Office: (949) 415-6975
- LinkedIn: linkedin.com/in/itsnicknguyen
Subject line: SMSWingman setup (or "I got a date, here's a tip"). Freelance web / AI / cloud work welcome too.
This path assumes a fresh-ish Ubuntu/Debian VPS (~ 2 GB RAM+ recommended; Chrome is hungry), SSH access as a normal user with sudo, and that you'll log into GV once through VNC.
Paths below use /opt/SMSWingman. Change them if you want.
- A VPS with a public IP (or Tailscale)
- A GV number on a Google account you can sign into (2FA ok)
- Node.js 18+
- Google Chrome (or Chromium the scripts can launch)
- An OpenAI-compatible LLM API key (OpenAI, Groq, Together, local gateway, etc.)
- A VNC client on your laptop (TigerVNC, RealVNC Viewer, TightVNC, …), or just a browser once Reconnect GV is running
ssh you@YOUR_VPS_IP
sudo apt update && sudo apt upgrade -ysudo apt install -y git curl build-essential python3 \
xvfb x11vnc novnc websockify \
fonts-liberation libnss3 libatk-bridge2.0-0 libgtk-3-0 \
libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 libgbm1 \
libasound2t64 || sudo apt install -y libasound2Install Google Chrome (stable):
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
| sudo gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" \
| sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt update
sudo apt install -y google-chrome-stableInstall Node.js 20 LTS:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -v && npm -vsudo mkdir -p /opt/SMSWingman
sudo chown "$USER":"$USER" /opt/SMSWingman
git clone https://github.com/moneyshotkid/SMSWingman.git /opt/SMSWingman
cd /opt/SMSWingmannpm install(postinstall also installs the client UI packages.)
cp .env.example .env
nano .env # or vim / whateverMinimum:
AUTH_USER=admin
AUTH_PASSWORD=<unique password — sample words from this repo are rejected>
SESSION_SECRET=<long random string — do not leave the example text>
# Default listen address is 127.0.0.1. Uncomment only if a proxy or Tailscale
# is in front and you accept the exposure:
# HOST=0.0.0.0
# OpenAI-compatible LLM (seeds System Settings; also editable in the UI)
LLM_API_KEY=sk-your-openai-compatible-key
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4o-mini
PORT=8787
DISPLAY=:99
GV_PROFILE=/home/YOUR_LINUX_USER/.google-voice-sms/chrome-profile
GV_DEBUG_PORT=9222
# Desktop embedded on Reconnect GV. See "Reconnect GV inside Wingman" below.
# GV_NOVNC_URL=http://127.0.0.1:6080/
# When you put HTTPS in front (Tailscale Serve, Caddy, Cloudflare Tunnel, …):
# COOKIE_SECURE=1Notes:
AUTH_*= login for the SMSWingman website, not Google.LLM_API_KEY+LLM_BASE_URL+LLM_MODELseed System Settings for any OpenAI-compatible provider. You can also paste/change all of this later under System Settings in the UI.- Never expose Chrome debug port 9222, VNC 5900, or noVNC 6080 to the public internet.
cd /opt/SMSWingman
export DISPLAY=:99
chmod +x scripts/*.sh
./scripts/restart-gv-chrome.sh
# or: ./scripts/start-gv-chrome.shAttach VNC to that same display (localhost only on the VPS), then expose it to the browser with noVNC:
pgrep -x x11vnc >/dev/null || \
x11vnc -display :99 -rfbport 5900 -localhost -nopw -forever -shared &
# Browser viewer used by Wingman's Reconnect GV page. Also localhost only.
pgrep -f "websockify .*6080" >/dev/null || \
websockify --web /usr/share/novnc 127.0.0.1:6080 127.0.0.1:5900 &/usr/share/novnc is the usual Debian/Ubuntu path. If websockify is missing, install the novnc and websockify packages from step 2.
You can sign in with a normal VNC client (steps 7–8) or, once Wingman itself is running, from Reconnect GV in the web UI (/gv-login). The in-app page is the one that works on a phone. See Reconnect GV inside Wingman.
Keep this SSH session open on your computer:
ssh -L 5900:127.0.0.1:5900 you@YOUR_VPS_IPWindows PowerShell (same idea):
ssh -L 5900:127.0.0.1:5900 you@YOUR_VPS_IPIf you use a key file:
ssh -i /path/to/your-key -L 5900:127.0.0.1:5900 you@YOUR_VPS_IP- On your laptop, open TigerVNC / RealVNC / TightVNC.
- Connect to
127.0.0.1:5900(orlocalhost:5900). - You should see the Xvfb desktop and Chrome.
- In that Chrome window, sign into the Google account that owns your GV number (complete 2FA).
- Open the GV Messages UI and confirm you see your threads (not a marketing dead-end page).
Blank VNC? Restart Chrome on the VPS:
export DISPLAY=:99
cd /opt/SMSWingman
./scripts/restart-gv-chrome.sh
pgrep -a x11vnc || x11vnc -display :99 -rfbport 5900 -localhost -nopw -forever -shared &Open another SSH session to the VPS:
ssh you@YOUR_VPS_IP
export DISPLAY=:99
cd /opt/SMSWingman
npm run loginWait until it prints that you're logged in. Session lives under GV_PROFILE.
Sanity check:
export DISPLAY=:99
npm run checkcd /opt/SMSWingman
npm run build
npm startThe API listens on 127.0.0.1:8787 unless you set HOST. Reach it with an SSH tunnel (ssh -L 8787:127.0.0.1:8787 you@YOUR_VPS_IP, then open http://127.0.0.1:8787) or put Caddy/nginx + HTTPS, or Tailscale, in front and set COOKIE_SECURE=1. Set HOST=0.0.0.0 only when that proxy or firewall is already in place.
Log in with AUTH_USER / AUTH_PASSWORD.
If the sidebar says GV not signed in, use the Reconnect GV banner — don't keep hitting Pull and wondering why nothing happens.
Dev mode (hot reload) if you're hacking on the box:
npm run dev
# UI often on :5173, API on :8787 — see terminal output- System Settings — paste your LLM key if needed; set base URL + model; write who you are (age, work, interests) and global guidelines.
- Add a target (phone + name) — tone, style, length, push-for-date, custom instructions, delay.
- Pull messages / sync that person.
- Review the three drafts, edit, send.
- Peek at memory after a few exchanges — edit it if the bot learned the wrong lore.
export DISPLAY=:99
cd /opt/SMSWingman
npm run gv -- send --to +15551234567 --message "Hi" --yes
npm run gv -- read --from +15551234567 --jsonUse systemd, pm2, or your favorite process manager for npm start, Xvfb/Chrome, x11vnc, and websockify. After a reboot you'll usually need display + Chrome (+ noVNC if you're signing in again) before Wingman can talk to GV.
Example sketch with pm2:
sudo npm i -g pm2
cd /opt/SMSWingman
pm2 start npm --name smswingman -- start
pm2 save(Still start Xvfb/Chrome via the scripts on boot — wire that however you prefer.)
When the GV session expires you do not need a separate VNC app. Sign in inside Wingman:
- Open the app and sign in with
AUTH_USER/AUTH_PASSWORD. - Go to Settings → Reconnect GV, tap the GV not signed in banner, or open
/gv-logindirectly. - The page embeds the noVNC desktop (
GV_NOVNC_URL, defaulthttp://127.0.0.1:6080/). The server turns that intovnc.htmlwithresize=scale. - Sign in on that desktop (2FA included). The page polls
GET /api/gv/statusand the banner clears onceloggedInis true.
There is a second path when GV is signed out: Reconnect Google Voice in the sidebar, on the signed-out banner, and in Settings. That button calls POST /api/gv/reconnect with { "confirm": true }, which runs gv-autofill (GV_AUTOFILL_BIN, default /root/.google-voice-sms/bin/gv-autofill) and then opens the in-app desktop at /gv-login. It does not open port 6080 on the public hostname. The helper path has to be an absolute path to a real file; the browser cannot choose the command, and the helper's output is not shown in the page. The in-app desktop is the same screen embedded at /gv-login.
Hand tool. resize=scale fits the whole remote screen in the iframe. noVNC's hand/pan tool is intentionally unused in that mode — panning is only for an unscaled desktop that is larger than the browser. Leave the noVNC scaling control on Scale. On a phone, pinch-zoom the browser if you need a closer look; don't switch scaling to None or you'll get the hand tool back.
GV_NOVNC_URL. http://127.0.0.1:6080/ is the websockify port on the machine running Wingman. That is correct when the browser is on that same machine. From a phone, 127.0.0.1 is the phone, so the embed will be blank. Either:
- Set an absolute URL the phone can open, usually over Tailscale:
GV_NOVNC_URL=http://wingman.tailnet.ts.net:6080/(still do not publish 6080 on the public internet), or - Reverse-proxy noVNC onto the same origin as Wingman and use a relative path:
GV_NOVNC_URL=/novnc/. The server then loads/novnc/vnc.htmland sets the websocketpathtonovnc/websockifyunless you already setpath. Proxy WebSocket upgrades, not just HTTP. If Wingman is HTTPS, the embed has to be HTTPS or that relative path — browsers block anhttp://iframe on anhttps://page.
GET /api/gv/status (session login required) returns { loggedIn, phase, cdpReady, novncUrl, scaling: "scale", chromeRestart, checkedAt }. loggedIn uses the same isLoggedIn check as npm run check. The poll does not navigate a tab that is already on a Google sign-in screen. A blank window is opened onto GV messages, and if Chrome's debug port is down the status check starts it.
Restart button (optional, off by default). Set GV_CHROME_RESTART=1 to show Restart GV Chrome. It is behind the same Wingman login as the rest of the API and only runs scripts/restart-gv-chrome.sh, or systemctl restart on GV_CHROME_SYSTEMD_UNIT when that is set. The unit name is checked against a safe pattern; the browser cannot pass a command. scripts/start-gv-chrome.sh is the boot helper — it exits immediately when Chrome is already listening, so the button does not use it. There is no sudo. If you don't set the flag, the page tells you to run the restart script over SSH.
- Do not publish ports 9222 (Chrome CDP), 5900 (VNC), or 6080 (noVNC) on the open internet. Localhost + SSH tunnel (or Tailscale) only. Wingman's login does not protect the noVNC port itself. If you reverse-proxy
/novnc/, that path is still the desktop: do not leave it reachable without the same login or a private network. - The server binds 127.0.0.1 unless
HOSTis set. Placeholder values forAUTH_PASSWORDandSESSION_SECRET(includingchange-me-nowandgenerate-a-long-random-string) are rejected. - Put a unique password on
AUTH_PASSWORD. Prefer HTTPS (COOKIE_SECURE=1) before you leave the LAN. - Do not run Wingman as root. The GV autofill helper under
/rootshould be executable by the dedicated user, or you should invoke it from an admin shell instead of the web UI. - This scrapes a consumer messaging UI. Treat account risk seriously.
| Problem | What to try |
|---|---|
npm not found |
Reinstall Node 18+ and open a new shell |
| GV login / "browser not secure" | Finish sign-in by hand in Reconnect GV or a VNC client, including 2FA |
| Reconnect GV iframe is blank on a phone | GV_NOVNC_URL is still 127.0.0.1 — use a Tailscale URL or /novnc/ (see above). Confirm websockify is listening on 6080 |
| Marketing page instead of Messages | Wrong Google account / not fully in GV — fix in VNC, then npm run login again |
check fails |
export DISPLAY=:99 then npm run login / npm run check |
page.goto timeout |
Restart ./scripts/restart-gv-chrome.sh; confirm CDP on 9222 |
| AI drafts fail | Key + LLM_BASE_URL + LLM_MODEL in .env or System Settings; check provider quota |
| Can't open the site | :8787 after npm start; :5173 often for npm run dev |
| Port in use | Change PORT in .env |
| Sync/send feels slow or flakes | Expected — it's a scrape. Retry; file an issue / PR when GV markup changes |
SMSWingman/
├── README.md
├── package.json
├── .env.example
├── send-gv-sms.mjs # GV CLI helpers
├── scripts/ # Xvfb + Chrome helpers (VPS)
├── server/ # Express API + SQLite
├── client/ # React (Vite) UI
└── data/ # local DB (runtime)
Issues and PRs welcome — especially when GV's website moves the cheese. Keep the project aimed at personal, consenting, one-to-one messaging. If the scrape breaks and you fix it, you're the real wingman.
- Unofficial. Not affiliated with Google or any LLM vendor.
- You are responsible for GV's terms, your LLM provider's terms, and messaging / consent laws where you live.
- Not for spam or abusive automation.
- Provided as-is. Authors aren't liable for account limits, lost messages, awkward dates, or other damages.
- Yes, it's a hack. A working one. Act accordingly.
MIT — see LICENSE.
Got a date out of it? Tip, donate, or hire me for the next hack. I need the work more than I need another triple-text.