mirror of
https://github.com/docwho76/sendmsg.git
synced 2026-08-31 01:41:10 -07:00
398 lines
14 KiB
Markdown
398 lines
14 KiB
Markdown
# sendmsg
|
|
|
|
**Universal message sender for Signal and SMS/iMessage.**
|
|
|
|
`sendmsg` is a command-line tool for sending messages via the [Signal REST API](https://github.com/bbernhard/signal-cli-rest-api) or locally via macOS [Messages](https://apps.apple.com/app/messages/id1092291483) (iMessage/SMS) through the [`imsg`] CLI.
|
|
|
|
It supports individual direct messages, Signal group broadcasts, file attachments, and bulk sending from CSV files — all with a single, consistent interface.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
- [Architecture](#architecture)
|
|
- [Dependencies](#dependencies)
|
|
- [Installation](#installation)
|
|
- [Usage](#usage)
|
|
* [Signal Messages](#signal-messages)
|
|
* [SMS / iMessage](#sms--imessage)
|
|
* [Bulk Send from CSV](#bulk-send-from-csv)
|
|
* [Management Commands](#management-commands)
|
|
- [CSV Format](#csv-format)
|
|
- [Configuration](#configuration)
|
|
- [Examples](#examples)
|
|
- [Error Handling](#error-handling)
|
|
- [Summary Output](#summary-output)
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ sendmsg (CLI) │
|
|
│ Python 3 script with argparse-based argument parsing │
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────────────────┐ │
|
|
│ │ --signal │ │ --sms │ │
|
|
│ │ │ │ │ │
|
|
│ │ POST JSON │ │ Call `imsg send` CLI │ │
|
|
│ │ to Signal │ │ │ │
|
|
│ │ REST API │ │ │ │
|
|
│ └──────┬───────┘ └────────────┬─────────────┘ │
|
|
│ │ │ │
|
|
│ ▼ │ │
|
|
│ (Docker) ▼ │
|
|
│ ┌───────────────────────┐ ┌─────────────────────┐ │
|
|
│ │ signal-cli-rest-api │ │ │ │
|
|
│ │ http://localhost:8080 │ │ macOS Messages │ │
|
|
│ │ (or SIGNAL_REST_URL) │ │ │ │
|
|
│ └───────────────────────┘ └─────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Signal path:** `sendmsg` → JSON HTTP POST → `signal-cli-rest-api` → Docker container → Signal network. Attachments are base64-encoded inline in the JSON request body (the API does not accept multipart uploads).
|
|
|
|
**SMS/iMessage path:** `sendmsg` → subprocess call → `imsg` CLI → macOS Messages framework → carrier/Apple.
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
### Required
|
|
|
|
| Dependency | Version | Purpose |
|
|
| ---------- | ----------------- | --------------------------------- |
|
|
| Python 3 | 3.9+ | Script runtime |
|
|
| macOS 12+ | Monterey or later | Required for iMessage/SMS support |
|
|
|
|
> `sendmsg` uses only the Python standard library — no third-party packages to install.
|
|
|
|
### Signal (optional — only needed for `--signal`)
|
|
|
|
| Dependency | Version | Purpose |
|
|
| ---------------------------- | ----------------------------- | ------------------------------ |
|
|
| Docker | 24+ | Container runtime |
|
|
| `signal-cli-rest-api` | latest | Signal REST API server |
|
|
| `signal-cli` (via container) | latest | Signal protocol implementation |
|
|
|
|
### SMS/iMessage (optional — only needed for `--sms`)
|
|
|
|
| Dependency | Version | Purpose |
|
|
| ---------- | ------- | --------------------------- |
|
|
| `imsg` CLI | latest | macOS Messages / SMS bridge |
|
|
|
|
### System
|
|
|
|
- **macOS** — The script is designed for macOS; SMS/iMessage uses the native Messages app.
|
|
- **Network access** — Required to reach the Signal REST API endpoint.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
1. **Place the script:**
|
|
|
|
```
|
|
cp sendmsg /usr/local/bin/sendmsg
|
|
chmod +x /usr/local/bin/sendmsg
|
|
```
|
|
|
|
2. **Ensure dependencies are installed:**
|
|
|
|
**For Signal:**
|
|
|
|
```
|
|
# Verify signal-cli-rest-api is running
|
|
curl -s http://localhost:8080/v1/accounts | python3 -m json.tool
|
|
```
|
|
|
|
**For SMS/iMessage:**
|
|
|
|
```
|
|
# Verify imsg is installed and available
|
|
imsg send --help
|
|
```
|
|
|
|
3. **Test the script:**
|
|
|
|
```
|
|
sendmsg --help
|
|
sendmsg --show-config # confirm where settings are being read from
|
|
```
|
|
|
|
---
|
|
|
|
## Usage
|
|
|
|
```
|
|
sendmsg [OPTIONS]
|
|
|
|
Messaging methods (choose one):
|
|
--signal Send via Signal
|
|
--sms Send via SMS/iMessage (macOS Messages)
|
|
--csv FILE Bulk send from a CSV file
|
|
|
|
Management commands (choose one):
|
|
--list-signal List linked Signal accounts
|
|
--link-signal Link a new device to your Signal account
|
|
--show-config Print resolved configuration and where each value came from
|
|
```
|
|
|
|
### Signal Messages
|
|
|
|
```
|
|
# Send a text message
|
|
sendmsg --signal --to +18885551212 --text "Hello from Signal!"
|
|
|
|
# Send to a group
|
|
sendmsg --signal --recipients group.TestGroupHash --text "Hello everyone!"
|
|
|
|
# Send with an attachment
|
|
sendmsg --signal --to +18885551212 --text "Check this out" --attach ~/photo.jpg
|
|
|
|
# Send multiple messages (concatenated with newlines)
|
|
sendmsg --signal --to +18885551212 --text "Line 1" --text "Line 2"
|
|
|
|
# Send multiple attachments
|
|
sendmsg --signal --to +18885551212 --text "Files" --attach ~/doc.pdf ~/pic.jpg
|
|
```
|
|
|
|
> Attachments are read, base64-encoded, and sent inside the JSON request to
|
|
> the Signal REST API. The original filename and detected MIME type are
|
|
> preserved so recipients see the correct file name and type.
|
|
|
|
### SMS / iMessage
|
|
|
|
```
|
|
# Send via iMessage (auto-detected)
|
|
sendmsg --sms --to +18885551212 --text "Hello via iMessage!"
|
|
|
|
# Force SMS service
|
|
sendmsg --sms --to +18885551212 --text "Hello via SMS!" --service sms
|
|
|
|
# Send with an attachment
|
|
sendmsg --sms --to +18885551212 --text "Photo attached" --file ~/photo.jpg
|
|
```
|
|
|
|
### Bulk Send from CSV
|
|
|
|
```
|
|
sendmsg --csv messages.csv # Send all rows
|
|
sendmsg --csv messages.csv --delay 2 # Wait 2 seconds between every send
|
|
```
|
|
|
|
### Management Commands
|
|
|
|
```
|
|
# List linked Signal accounts
|
|
sendmsg --list-signal
|
|
|
|
# Link a new device
|
|
sendmsg --link-signal
|
|
sendmsg --link-signal --name "my-laptop"
|
|
|
|
# Show the active configuration and its sources
|
|
sendmsg --show-config
|
|
sendmsg --show-config -v # also checks whether the Signal REST API is reachable
|
|
```
|
|
|
|
---
|
|
|
|
## CSV Format
|
|
|
|
Create a CSV file with the following columns:
|
|
|
|
| Column | Required | Description |
|
|
| ----------- | -------- | ----------------------------------------------------------------------------- |
|
|
| `method` | Yes | `signal` or `sms` (defaults to `signal` if left blank) |
|
|
| `recipient` | Yes | Phone number, or a Signal group ID (`group.XXXX` or a raw group key) |
|
|
| `name` | No | Display name shown during status output (e.g., "Alice", "Marketing Group") |
|
|
| `message` | Yes | The message text to send |
|
|
| `account` | No | Signal account phone number (defaults to `$SIGNAL_ACCOUNT` / config value) |
|
|
| `service` | No | SMS service: `imessage`, `sms`, or `auto` (SMS only) |
|
|
| `file` | No | Path to an attachment file. `~` and environment variables are expanded. |
|
|
| `delay` | No | Seconds to wait after this row is sent (overrides the global `--delay`). |
|
|
|
|
**Notes**
|
|
|
|
- A `message` is required on every row; rows with no message are skipped and reported.
|
|
- Group recipients are auto-detected: any `recipient` that is not a `+`-prefixed phone number is treated as a Signal group ID.
|
|
- File paths starting with `~` are expanded to the home directory of the user running the script. If a named attachment cannot be found, a warning is printed and the message is still sent without it.
|
|
|
|
### Example CSV
|
|
|
|
```
|
|
method,recipient,name,message,account,service,file,delay
|
|
signal,+18885551111,Alice,Hello via Signal,+18885551111,,,
|
|
signal,group.ZzBHd3NZO...,Team Alert,Morning update for the team,,,,
|
|
sms,+18885552222,Bob,SMS test,,,~/pic.jpg,
|
|
signal,+18885553333,Carol,With a delay after this row,,,,3
|
|
```
|
|
|
|
### Status Output
|
|
|
|
During a bulk send, each row prints its name (if provided):
|
|
|
|
```
|
|
[1/4] SIGNAL → Alice (+18885551111)
|
|
[2/4] SIGNAL → Team Alert (group.ZzBHd3NZO...)
|
|
[3/4] SMS → Bob (+18885552222)
|
|
[4/4] SIGNAL → Carol (+18885553333)
|
|
```
|
|
|
|
If no `name` is provided, the recipient is shown instead.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
Settings are read from a config file at `~/.sendmsg.conf`, with environment
|
|
variable and built-in fallbacks.
|
|
|
|
**Resolution order:** environment variable → config file → built-in default.
|
|
|
|
### Config file format (INI-style)
|
|
|
|
```
|
|
[settings]
|
|
signal_rest_url = http://localhost:8080
|
|
signal_default_account = +1234567890
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
| ----------------- | ----------------------- | ----------------------------------- |
|
|
| `SIGNAL_REST_URL` | `http://localhost:8080` | URL of the Signal REST API server |
|
|
| `SIGNAL_ACCOUNT` | `+1234567890` | Default Signal account phone number |
|
|
|
|
### Inspecting the active configuration
|
|
|
|
Use `--show-config` to print the resolved values and exactly where each one
|
|
came from (environment, config file, or built-in default):
|
|
|
|
```
|
|
$ sendmsg --show-config
|
|
==================================================
|
|
⚙️ sendmsg configuration
|
|
==================================================
|
|
Version: 4.3.0
|
|
|
|
Config file: /Users/you/.sendmsg.conf (found)
|
|
|
|
signal_rest_url: http://localhost:8080
|
|
└─ source: config (/Users/you/.sendmsg.conf)
|
|
|
|
signal_default_account: +18885551212
|
|
└─ source: env ($SIGNAL_ACCOUNT)
|
|
|
|
Resolution order: env var > config file > built-in default
|
|
==================================================
|
|
```
|
|
|
|
Add `-v` to also probe whether the Signal REST API is currently reachable.
|
|
|
|
### Override Example
|
|
|
|
```
|
|
export SIGNAL_REST_URL=http://localhost:8082
|
|
export SIGNAL_ACCOUNT=+18885551212
|
|
sendmsg --csv messages.csv
|
|
```
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Daily Broadcast
|
|
|
|
```
|
|
method,recipient,name,message,account,service,file,delay
|
|
signal,group.ZzBHd3NZO...,Daily Update,Good morning team! Here's your daily briefing.,+18002222222,,,
|
|
```
|
|
|
|
### Personalized Outreach
|
|
|
|
```
|
|
method,recipient,name,message,account,service,file,delay
|
|
signal,+18885551111,John,Hi John hope you're doing well,,,,
|
|
sms,+18885552222,Jane,Hey Jane just checking in,,,,
|
|
signal,+18885553333,Alex,Alex don't forget the meeting tomorrow at 3pm,,,,
|
|
```
|
|
|
|
> Avoid commas inside the `message` field unless the field is quoted, since
|
|
> commas are the CSV column separator.
|
|
|
|
### With Attachments
|
|
|
|
```
|
|
method,recipient,name,message,account,service,file,delay
|
|
signal,+18885551111,John,Here's the report you asked for,+18002222222,,~/Downloads/report.pdf,
|
|
sms,+18885552222,Jane,Photo from the event,,,~/Photos/event.jpg,
|
|
```
|
|
|
|
### With Delays
|
|
|
|
```
|
|
method,recipient,name,message,account,service,file,delay
|
|
signal,+18885551111,Alice,First message,,,,
|
|
signal,+18885552222,Bob,Second message after a 3s pause,,,,3
|
|
signal,+18885553333,Charlie,Third message,,,,
|
|
```
|
|
|
|
A per-row `delay` value takes precedence over the global `--delay` flag for
|
|
that row.
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
- **Unknown method:** Rows with invalid `method` values are skipped and reported in the summary.
|
|
- **Empty message:** Rows without a `message` are skipped and reported.
|
|
- **Missing attachment:** If a named file cannot be found, a warning is printed and the message is sent without the attachment.
|
|
- **Failed sends:** Failed attempts are counted and listed in the summary.
|
|
- **Exit codes:** The script exits with `1` if any rows fail, `0` on full success.
|
|
|
|
---
|
|
|
|
## Summary Output
|
|
|
|
After processing all rows, a summary is printed:
|
|
|
|
```
|
|
==================================================
|
|
📊 CSV Send Summary
|
|
==================================================
|
|
Total rows: 4
|
|
✅ Success: 3
|
|
❌ Failed: 1
|
|
|
|
Errors:
|
|
• Row 2: failed to send
|
|
==================================================
|
|
```
|
|
|
|
---
|
|
|
|
## Changelog
|
|
|
|
### 4.3.0
|
|
- **Fixed:** Signal attachments now send correctly. Files are base64-encoded and delivered in the JSON request body via `base64_attachments`; the previous multipart upload was rejected by the Signal REST API with HTTP 400.
|
|
- Attachment filename and MIME type are now preserved using a data-URI form.
|
|
|
|
### 4.2.0
|
|
- **Added:** `--show-config` to report resolved settings and their sources (`-v` also checks REST API reachability).
|
|
- Config resolution rewritten to correctly source-track values and avoid empty values silently falling through to defaults.
|
|
|
|
### 4.1.0
|
|
- **Fixed:** CSV attachment paths using `~` are now expanded, so Signal/SMS attachments from CSV rows are no longer silently dropped.
|
|
- **Fixed (issue #3):** Group messages are detected robustly; raw (non-`group.`) group IDs are no longer mis-sent down the direct-message path.
|
|
- **Fixed:** Multipart text/bytes join crash; consistent `+` normalization of account numbers; global `--delay` now applies between rows; missing files are skipped with a warning instead of being passed to the sender; duplicate/dead Signal send branches collapsed.
|
|
|
|
---
|
|
|
|
*For support or issues, see <https://xkcd.com/627/>*
|