An open-on-demand SMS terminal for a Linux laptop with a cellular modem.
No daemon. No notifications. No buzzing pocket slot-machine. You read and reply to texts when you choose to open it — the rest of the time it's not running. Built for using a mini-laptop (a GPD Pocket 4, in my case) as an intentional comms device instead of carrying a smartphone.
It drives the modem through ModemManager, so it works with any MM-supported cellular modem (Quectel EC25/EG25, SIMCom SIM7600, etc.) that has a SIM and is registered to a network.
B00 · GPD MESSAGES
T-Mobile · registered · sig 62%
────────────────────────────────────────
📥 Inbox
✉️ New message
🚫 Blocked
🔄 Refresh
🚪 Quit
- Pull-based by design — incoming texts buffer on the SIM/network while the lid is closed and appear when you open the app. There is no background notifier, on purpose.
- Read / open / reply to messages in an
fzfinbox. - Send new texts (robust: arbitrary text with commas, quotes, apostrophes — see How it works).
- Local block list — block a number and their texts auto-delete the moment you open the app. Review and un-block from a dedicated 🚫 Blocked screen.
- One-click launcher — installs a
GPD Messagesdesktop entry (app menu + double-clickable). - CLI too — scriptable subcommands alongside the TUI.
ModemManager(mmcli) — the modem stack, with a registered SIMbusctl(systemd) — used for sending (see below)fzf— the interactive inbox/menus- A terminal emulator (konsole / alacritty / kitty / foot / …) for the desktop launcher
git clone https://github.com/Peterc3-dev/gpd-sms
cd gpd-sms
./install.shInstalls gpd-sms to ~/.local/bin and a GPD Messages launcher to your app menu. Or just copy gpd-sms somewhere on your PATH and run it.
Launch the TUI by clicking GPD Messages, or run gpd-sms. Arrows + Enter to navigate, Esc to back out.
CLI subcommands:
gpd-sms interactive TUI (default)
gpd-sms status one-line modem / signal status
gpd-sms list print the inbox
gpd-sms read <id> print one message
gpd-sms send <num> <text...> send a text
gpd-sms block <num> block a number (their texts auto-delete on open)
gpd-sms unblock <num> remove a block
gpd-sms blocked list blocked numbers
- Reading uses
mmcli(--messaging-list-sms,mmcli -s <id> -K). - Sending goes through
busctlcalling ModemManager'sMessaging.Createover D-Bus, notmmcli --messaging-create-sms. That's deliberate: mmcli'skey=valuecreate string splits on commas, so any message containing a comma fails to parse. The D-Bus path passes the text as a proper string, so commas/quotes/apostrophes all survive. - The modem index is discovered fresh each run (it changes across re-enumeration).
- The block list lives at
~/.config/gpd-sms/blocked(one number per line). Blocked senders' messages are deleted on inbox/list open.
SMS: yes. Send and receive are reliable once the modem is registered to the network.
Voice calls: no (at least not via the modem on a VoLTE-only carrier). Modern US carriers are VoLTE-only, VoLTE/IMS is gated behind a device whitelist most USB/IoT modems aren't on, ModemManager doesn't orchestrate IMS, and routing call audio off a USB modem is fragile. If you need voice on the same number, pair this with a VoIP layer (e.g. Google Voice) or a basic VoLTE phone — this tool is the text half.
Block is local. From a SIM-in-the-laptop setup there's no carrier-level block to reach — blocked texts still arrive at the carrier/modem; the app silently drops them from your view. For a true network block, use your carrier's app.
Signal-dependent. Delivery timing tracks your coverage — if the modem is mid-searching, sends fail (retry) and inbound texts queue until it re-registers. The header shows the live state.
This repo also ships two small cellular utilities that pair with gpd-sms:
siminfo SIM presence, ICCID/IMSI, operator, signal, data bearer
siminfo --at raw AT-command dump (diagnostics; needs a free AT port)
siminfo --fix recover a "sim-missing" Quectel modem by disabling the
hardware SIM-detect pin (AT+QSIMDET=0,0) and resetting it
--fix is for a specific, common gotcha: some laptop/M.2 SIM trays don't wire
the modem's SIM-detect pin, so a Quectel EC25/EG25 reports sim-missing even
with a perfectly good card inserted. AT+QSIMDET=0,0 tells it to read the SIM
off the data lines instead; the setting persists in the modem's NV. Installed by
install.sh alongside gpd-sms.
A NetworkManager dispatcher that keeps a cellular connection down while Wi-Fi
is up and brings it up when Wi-Fi drops — so a metered SIM isn't used
unless you actually need it. Config-driven (set your connection UUID + Wi-Fi
device in /etc/cellular-failover.conf). See failover/README.md.
Modem is visible to the OS but won't register to any network — the Quectel
EC25/EG25 ships FCC-locked, and ModemManager needs an unlock script linked
in for it. Vendor 2c7c = Quectel:
sudo ln -s /usr/share/ModemManager/fcc-unlock.available.d/2c7c \
/etc/ModemManager/fcc-unlock.d/
sudo systemctl restart ModemManagerWithout this the modem shows up in mmcli but silently refuses to connect — it
looks like a SIM or code problem but it's the FCC lock. (Credit: the
Gentoo GPD Pocket 4 wiki.)
gpd-sms shows the SIM as missing — see siminfo --fix above (un-wired
SIM-detect pin).
MIT — see LICENSE.