Skip to main content

Notify yourself via Signal (Note-to-Self), pure Python: QR device-linking, native send/receive, quiet hours, batching, and file-based alert dedupe.

Project description

signal-notify

CI Python 3.9+ License: AGPL v3

Notify yourself via Signal with full push notifications (lock screen + Apple Watch). Messages land in your own Note-to-Self chat. No second phone number, no bot account, no third-party push service required.

A phone bridge for headless AI agents. signal-notify turns the Signal app you already have into a two-way remote interface for an AI agent (Claude Code, Codex, or any custom harness) running on a server or VM. The agent pushes you updates and questions; you reply in your own Note-to-Self chat; the agent reads your reply and acts. It's an alternative to needing a dedicated mobile app (like the Claude app) or a Tailscale-style tunnel just to reach your agent from your phone — the transport is Signal, end-to-end encrypted, reachable from anywhere your phone has signal. See 🤖 AI Agent Bridge below.

100% pure Python, no external Signal client, no Java, no Rust. Linking, sending and receiving all talk directly to Signal's servers over HTTPS/WebSockets. Every piece of protocol logic — X3DH/PQXDH, the Double Ratchet, the post-quantum primitives Signal mandates (round-3 Kyber-1024 and the Sparse Post-Quantum Ratchet / SPQR), protobuf, transport, padding, session management — is Python. The only compiled dependency is cryptography (a standard pip wheel, used for X25519/AES/HKDF). No JVM, no subprocess, no compiled extensions to build.


📖 Quick Links


🚀 Installation

From a local clone

git clone https://github.com/ricardodeazambuja/signal-notify.git
cd signal-notify
pip install -e .

Directly from GitHub (no manual clone)

pip install "git+https://github.com/ricardodeazambuja/signal-notify.git"

This installs the signal-notify package and CLI straight from the repo (pip clones it internally). Python dependencies: cryptography>=38 (X25519/AES/HKDF), websockets, qrcode, PyYAML. No external binaries, no Java, no Rust toolchain — a plain pip install is everything you need, including the post-quantum crypto.

ARM boards (Raspberry Pi, Jetson, …)

signal-notify itself is a pure-Python (py3-none-any) package; the only compiled dependency is cryptography, and PyPI ships prebuilt cryptography wheels for both ARM flavours:

  • 64-bit ARM (aarch64): wheels go back to manylinux2014 (glibc ≥ 2.17), so a plain pip install just works on 64-bit Raspberry Pi OS, Jetson (JetPack with Python ≥ 3.9), and essentially any aarch64 Linux — nothing is compiled locally.
  • 32-bit ARM (armv7l, e.g. 32-bit Raspberry Pi OS): recent cryptography releases ship manylinux_2_31_armv7l wheels, covering Raspberry Pi OS Bullseye (2021) and newer with CPython ≥ 3.9. On older 32-bit images (glibc < 2.31), pip would fall back to a source build (Rust toolchain + OpenSSL headers) — avoid that by using piwheels (preconfigured on Raspberry Pi OS) or the distro package (apt install python3-cryptography, version ≥ 38).

The other dependencies never need a compiler: qrcode and websockets ship pure-Python wheels (websockets' C speedups are optional), and PyYAML is on piwheels/apt for 32-bit ARM.

Post-quantum crypto is pure Python. Kyber-1024 and SPQR are implemented in signalnotify/native/pure/ and validated byte-for-byte against Signal's own Rust libraries (see caveat #19). The Rust bindings under rust/ are kept only as differential-test oracles; you never need to build them to use signal-notify. To run the cross-implementation tests against them, build with PYTHON=$(which python) rust/build.sh (needs a Rust toolchain + maturin) and set SIGNALNOTIFY_SPQR_BACKEND=rust / SIGNALNOTIFY_KEM_BACKEND=rust.


⚡ Quick Start

1. Link your device

Scan the terminal QR code using your phone's Signal app (Settings → Linked Devices → Link New Device):

signal-notify link -n "server-alerts"

2. Send a test message

signal-notify send -m "Deployment complete! ✅"

3. Send to another recipient

signal-notify send -m "Hello there" --to "+15551234567"

4. Receive replies (bidirectional, incl. Note-to-Self)

# Drain pending messages once (ideal for cron): prints each message.
signal-notify receive

# Only the replies you typed in your own Note-to-Self chat on your phone:
signal-notify receive --note-to-self

Receiving decrypts messages natively (responder X3DH/PQXDH + Double Ratchet). The Note-to-Self channel works because when you type in your own Note-to-Self chat, your phone broadcasts a sync transcript to this linked device — that sync transcript is the inbound half of the loop.


🤖 AI Agent Bridge

Give a headless agent (Claude Code, Codex, or your own harness) a two-way channel to your phone over Signal — no dedicated app, no tunnel.

Connect your Signal app (one time)

pip install -e .                              # pure Python; post-quantum crypto included
signal-notify link -n "my-agent"              # prints a QR

On your phone: Signal → Settings → Linked Devices → Link New Device → scan the QR. signal-notify doctor confirms the connection. Your account state lives (owner-only) under ~/.local/share/signal-notify/data/back it up before re-linking.

Two building blocks

from signalnotify import send_message, receive

# 1. Push a message to your phone (fire-and-forget)
send_message("Build finished ✅ — deploy? (yes/no)")

# 1b. Push a file too — a plot, a screenshot, a log (encrypted client-side).
send_message("today's error rate", attachments=["plot.png"])

# 2. Wait for your reply, typed in Note-to-Self on the phone
#    drain=False keeps ONE connection open and returns as soon as you reply.
reply = next((m.body for m in
              receive(drain=False, max_messages=1, wait=300) if m.note_to_self), None)
if reply and reply.lower().startswith("y"):
    deploy()

Ready-to-run examples

  • examples/agent_chat.py — send-then-wait. Exposes notify(text) and ask(prompt, timeout) (send a question, block until you reply on the phone). Run it for an interactive terminal ↔ phone chat.
  • examples/agent_daemon.py — always-on. A persistent listener that dispatches every Note-to-Self command you send from your phone to a handler (wire your agent/LLM in). This is your phone "remote."
python examples/agent_chat.py       # you type here → phone → you reply on phone → prints here
python examples/agent_daemon.py     # then Note-to-Self "status" / "echo hi" from your phone

Why this works: your phone (the primary device) and this linked device are two devices on one Signal account, so anything you type in Note-to-Self syncs to the agent, and anything the agent sends to Note-to-Self shows up (and notifies) on your phone. It's end-to-end encrypted and reachable anywhere your phone has Signal. See Using, Extending & Customizing for the full API.


🏠 Home Assistant

Signal makes a great Home Assistant notification channel: end-to-end encrypted, free, no bot number, and it lands on your lock screen. The simplest hookup is a shell_command:

shell_command:
  signal_notify: "signal-notify send -m '{{ message }}'"

…called from any automation with service: shell_command.signal_notify. The Home Assistant guide covers a first-class notify.signal service, sending camera snapshots as encrypted attachments, alert batching/quiet hours via run, and a two-way daemon that turns Note-to-Self replies into HA service calls.


🚨 Config-Driven Alert Monitoring (run)

signal-notify includes a powerful diff-based alerting engine designed for cron jobs. It compares an active alert list against a notified history file, pushing only new alerts and clearing resolved ones to ensure you get notified exactly once per alert instance.

Copy the template and edit your own copy:

cp notify.example.yaml notify.yaml      # then put your number/recipients in notify.yaml
signal-notify run --config notify.yaml --active active.txt --notified notified.txt

Note: notify.yaml (and *.local.yaml) is git-ignored on purpose — it holds your personal number / recipients, so it never lands in the repo. Only the placeholder notify.example.yaml is tracked. Your Signal account keys live under ~/.local/share/signal-notify/data and are likewise never committed.

Example notify.yaml

channels:
  signal:
    enabled: true
    note_to_self: true

🐍 Python API Usage

You can also import and use the library directly inside your Python applications:

from signalnotify import send_message, send

# Send a single message to Note-to-Self
send_message("Server backup successful! ✅")

# Send batched alerts with a header
send(["disk 92%", "load 14.2"], header="host01")

Receiving (bidirectional)

from signalnotify import receive, receive_note_to_self

# One-shot drain of whatever is queued:
for m in receive():
    print(m.timestamp, m.source, m.body)

# Just the commands you typed into your Note-to-Self chat on your phone:
for m in receive_note_to_self():
    handle_command(m.body)

Each item is a Message (timestamp, body, source, source_name, note_to_self, group_id, raw, attachments). receive() is one-shot — call it once per cron cycle. A thin listen(callback) loop is also provided for daemon-style use. The server acks-and-deletes on receive; pass journal=True to have every parsed message appended to <account>.inbox.jsonl before it is acked, so a crash downstream can never lose one. Envelopes that cannot be decrypted (e.g. sealed-sender messages from contacts) are always preserved raw in <account>.undecryptable.jsonl instead of being destroyed by the ack.


⚠️ Gotchas & Tips

  • Notification Body Previews: If you receive notifications but cannot see the message body, make sure you configure your phone's notifications preview setting. See the Tutorial for details.
  • Session De-authorization: Signal will de-authorize linked secondary devices if they remain unused for a prolonged period. If notifications stop delivering, simply re-run the link command and scan the QR code again.
  • Security: Device key credentials and session files are saved securely under owner-only read/write permissions (0o600) inside ~/.local/share/signal-notify/data (override with SIGNALNOTIFY_DATA_DIR; an account store found at the legacy pre-1.0 location is migrated here automatically, once).

🗺️ Future Developments

The two post-quantum primitives Signal mandates — round-3 Kyber-1024 and SPQR — were originally in-process Rust (pyo3) bindings. They are now reimplemented in pure Python under signalnotify/native/pure/ (mlkem768.py, kyber1024.py, spqr.py and helpers), making signal-notify 100% pure Python with zero compiled extensions to build — no Rust toolchain, no maturin, no platform-specific wheels. The pure implementation is the default; the Rust bindings under rust/ are retained only as differential-test oracles (keys, ciphertexts, wire messages and serialized state are byte-compatible, so a live session can even move between the two mid-conversation). See caveat #19 for the security posture (notably: the pure code is not constant-time).

Remaining ideas: optional acceleration of the erasure-coding hot path (only material under heavy packet loss), and PyPI publishing.


💖 Credits & Acknowledgments

This project is inspired by and built upon the design of these outstanding open source repositories:

  • AsamK/signal-cli — Historical reference for the account-store layout and provisioning patterns; signal-notify no longer uses or shares state with it.
  • signalapp/libsignal — The cryptographic protocol specification defining the X3DH key agreement and Double Ratchet systems.
  • signalapp/SparsePostQuantumRatchet — Signal's post-quantum triple-ratchet crate. signalnotify/native/pure/spqr.py is a faithful pure-Python port of v1.5.1 (byte-compatible state and wire format); the rust/spqr_py oracle binds the original for differential testing.

License

AGPL-3.0-or-later — see LICENSE. Same license as the Signal source it interoperates with and derives from: libsignal, Signal-Android, Signal-Server, and the SparsePostQuantumRatchet crate — of which signalnotify/native/pure/spqr.py is a direct pure-Python port (the optional rust/spqr_py test oracle still binds the crate itself) — are all AGPL-3.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

signal_notify-0.1.0.tar.gz (125.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

signal_notify-0.1.0-py3-none-any.whl (109.6 kB view details)

Uploaded Python 3

File details

Details for the file signal_notify-0.1.0.tar.gz.

File metadata

  • Download URL: signal_notify-0.1.0.tar.gz
  • Upload date:
  • Size: 125.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for signal_notify-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2216b7d7455dde628ae5625306dc9e9395699d94ac58b947bcd296cbb7b969fe
MD5 eb8ff718dc16db11484b75c9f63e882d
BLAKE2b-256 9017348ffaeba68daef1c82e9cd68f2c230649ff6020955a31a451ab0acb1458

See more details on using hashes here.

Provenance

The following attestation bundles were made for signal_notify-0.1.0.tar.gz:

Publisher: wheels.yml on ricardodeazambuja/signal-notify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file signal_notify-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: signal_notify-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 109.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for signal_notify-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 95b7cf43b2fc5bd53de160a3072d597e48d5b294d7ab173df343fba636d17c37
MD5 78c3682676cd426a6bdc9ded0dea1e14
BLAKE2b-256 7110c1396f69f0fb321ca0cab03e0bddca5407d97c7b56e700685ca4be962934

See more details on using hashes here.

Provenance

The following attestation bundles were made for signal_notify-0.1.0-py3-none-any.whl:

Publisher: wheels.yml on ricardodeazambuja/signal-notify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page