A decentralized, stateless messaging system for transmitting encrypted messages between systems using a shared ID. Pulse leverages Nostr relays to enable secure, peer-to-peer communication without requiring centralized servers or maintaining connection state.
- Encrypted Messaging: AES-256-GCM encryption with SHA256-derived keys
- Decentralized: Uses Nostr relay infrastructure for message distribution
- Multiple Modes: Send, retrieve, listen, and interactive chat modes
- Configurable: Customize relays, encryption keys, and more via
pulse.conf - Verbose Mode: Full relay status reporting with timing and error details
- Cross-Platform: Works on Windows, macOS, and Linux
Download the latest precompiled binary from the Auchrio/Pulse GitHub releases page:
- Visit https://github.com/Auchrio/pulse/releases
- Download the binary for your platform:
pulse.exe- Windowspulse- macOS/Linux
- Extract the file to a location in your
PATHor keep it in your working directory - (Optional) Generate a config file:
./pulse --generate-config
Requires Go 1.25 or later:
git clone https://github.com/Auchrio/pulse.git
cd pulse
go build -o pulseSend a message:
pulse myid "Hello, World!"Retrieve the most recent message:
pulse myidListen for incoming messages (30-second timeout by default):
pulse myid -l <timeout>Interactive chat mode:
pulse myid -c usernameAdd the -v flag to any command to see detailed relay status:
pulse myid "Important message" -vOutput includes:
- Each relay connection status (✓ success, ✗ error/timeout)
- Response time from each relay
- Error messages (if any)
- Total operation time
Example:
Sending message...
[✓] wss://relay.damus.io 645ms (published)
[✓] wss://nos.lol 420ms (published)
[✓] wss://relay.snort.social 459ms (published)
Total time: 420ms
Success
Total operation time: 654ms
Send an encrypted message to an ID:
pulse <id> "<message>" [options]Example:
pulse alice "Meet me at the usual place" -v- Encrypts the message using the ID as the encryption key
- Publishes to all configured relays
- Waits up to 10 seconds for at least one relay to accept the message
- Returns immediately upon first successful publish with
-vshowing timing
Get the most recent message sent to an ID:
pulse <id> [options]Example:
pulse alice -v- Queries all configured relays for messages
- Waits up to 5 seconds for relay connections
- Collects responses for 300ms to ensure message freshness
- Returns the message with the most recent timestamp
- Outputs only the decrypted message (or error if none found)
Listen for incoming messages on an ID with configurable timeout:
pulse <id> -l [options]
pulse <id> --listen [options]Examples:
# Listen with default timeout (from config, default 30 seconds)
pulse alice -l
# Listen with custom 60-second timeout
pulse alice -l -t 60
# Listen with no timeout (waits indefinitely)
pulse alice -l -t 0
# Listen with verbose output
pulse alice -l -vFeatures:
- Subscribes to all configured relays
- Waits for new messages arriving after the command starts
- Returns immediately when first message is received
- Ignores messages from the same session (deduplication)
- Timeout configurable via
-tflag orlisten-timeoutin config
Timeout Options:
- No flag: Uses
listen-timeoutfrompulse.conf(default: 30 seconds) -t 0: No timeout - waits indefinitely for a message-t N: Waits N seconds for a message-t -1: Uses config default (same as no flag)
Interactive bidirectional chat on an ID:
pulse <id> -c [username] [options]
pulse <id> --chat [username] [options]Example:
pulse alice-bob-channel -c Alice- Loads message history from relays
- Displays previous messages
- Enters interactive prompt for sending new messages
- Shows incoming messages in real-time
- Auto-timestamps messages with
[HH:MM] Username: Messageformat
Features:
- Message deduplication (doesn't show duplicates from multiple relays)
- Prevents echoing your own messages back
- Clean terminal interface with message refresh
Pulse can be configured via pulse.conf in the same directory as the executable.
pulse --generate-config
# or
pulse -gThis creates pulse.conf with all settings and helpful comments.
pulse.conf uses simple key = value format:
# List of Nostr relays (comma-separated)
relays = wss://relay.damus.io, wss://nos.lol, wss://relay.snort.social
# Maximum number of messages to retrieve from history
history-limit = 5
# Secret key for encryption (used with message ID to derive encryption key)
user-secret = super-secret-key
# Default username to use in chat mode (optional)
# If set, skips the username prompt unless overridden from command line
default-username = MyUsername
# Listen timeout in seconds (for -l flag, 0 = no timeout)
listen-timeout = 30| Option | Type | Default | Description |
|---|---|---|---|
relays |
string (comma-separated) | wss://relay.damus.io, wss://nos.lol, wss://relay.snort.social |
List of Nostr relay URLs to connect to |
history-limit |
int | 5 |
Maximum number of messages to fetch from history |
user-secret |
string | super-secret-key |
Master secret for deriving encryption keys (change this!) |
default-username |
string | (none) | Default username for chat mode (skips prompt if set) |
listen-timeout |
int | 30 |
Default timeout in seconds for listen mode (0 = no timeout) |
Note: If pulse.conf doesn't exist, built-in defaults are used. Only override values you need to change.
Flags:
-c, --chat Enter chat mode
-l, --listen Listen for a new message
-t, --listen-timeout Listen timeout in seconds (0 = no timeout, -1 = use config default)
-v, --verbose Verbose output with relay status and timing
-g, --generate-config Generate pulse.conf with default settings
-h, --help Show help message
Pulse uses industry-standard encryption:
- Algorithm: AES-256-GCM (NIST-approved Galois/Counter Mode)
- Key Derivation: SHA256(ID + UserSecret)
- Nonce: Randomly generated for each message
- Tag Tagging: Messages are tagged with the hashed key for relay filtering
Important:
- Change
user-secretinpulse.confto something unique - Users sharing the same secret and ID can read each other's messages (this is intentional)
- The ID itself does NOT need to be secret
- Nostr messages are timestamped by relays
-
Sending:
pulse id "message"- Derives encryption key from
id + user-secret - Encrypts message with AES-256-GCM
- Publishes to all configured Nostr relays
- Returns when first relay accepts (or timeout)
- Derives encryption key from
-
Retrieving:
pulse id- Queries all relays for messages tagged with hashed encryption key
- Waits up to 300ms for all relays to respond
- Selects message with most recent timestamp
- Decrypts and returns
-
Listening:
pulse id -l- Subscribes to relays from current time forward
- Waits for new messages (30-second timeout)
- Returns first message received
-
Chatting:
pulse id -c username- Loads history, then subscribes for live updates
- Forwards all user input to relays
- Shows incoming messages in real-time
- Each message is a Nostr event (Kind 1: Text Note)
- Messages include a tag with the hashed encryption key
- Messages include a timestamp set by the relay
- Messages are replicated across relays (with delays)
Pulse comes pre-configured with reliable public Nostr relays. You can customize by editing pulse.conf:
Popular Nostr Relays:
wss://relay.damus.io- Widely used, good uptimewss://nos.lol- Community relay, fast responseswss://nostr.wine- Stable, good performancewss://nostr.band- Good for discoverywss://relay.snort.social- Popular, reliable
$ pulse alice-workspace "Project deadline moved to Friday"
Success$ pulse system-notifications -v
Retrieving message...
[✓] wss://relay.damus.io 591ms (message retrieved)
[✓] wss://nos.lol 404ms (message retrieved)
[✓] wss://relay.snort.social 493ms (message retrieved)
Total time: 404ms
System update available: v2.1.0
Total operation time: 591ms$ pulse team-channel -c Isaac
Loading history...
----- Previous Messages -----
[15:22] Bob: Meeting in 5 minutes
[15:24] Alice: On my way
--- Connected as [Isaac] ---
> Hi everyone!
[15:25] Isaac: Hi everyone!
[15:26] Bob: Hey Isaac!
> Thanks for the update
[15:26] Isaac: Thanks for the update
[15:27] Alice: You're welcome$ pulse workflow-status -l -v
Retrieving message...
[✓] wss://relay.damus.io 145ms (message retrieved)
[✓] wss://nos.lol 123ms (message retrieved)
Deployment successful: 3 services green
Total operation time: 201ms- Cause: Message hasn't propagated to queried relays yet
- Solution: Wait a few seconds and try again, or check relay connectivity with
-vflag
- Cause: Relay is down or unreachable
- Solution: Add more relays to
pulse.conf, or check the relay's status page
- Cause: This is normal - means operation succeeded on another relay and remaining connections were cancelled
- Solution: This is expected behavior, not an error
- Cause: Wrong
user-secretor corrupted message data - Solution: Ensure all parties use the same
user-secretvalue
- Send: ~200-700ms (depends on relay response times)
- Retrieve: ~300-600ms (waits 300ms for all relays to respond)
- Listen: ~100-300ms (depends on relay propagation)
- Chat: Real-time, limited by network latency
Retrieval waits for all relays to respond (up to 300ms) to ensure the most recent message is returned, even if a slower relay has a newer version.
Refer to the LICENSE file in the repository.
Contributions welcome! Submit issues and pull requests at github.com/Auchrio/pulse
Pulse provides encryption in transit and at rest on relays. However:
- Relay operators can see the metadata (timestamps, IP addresses if over non-Tor)
- Message content is encrypted, but the fact that a message exists is visible
- Use multiple relays for redundancy but understand they're third-party services -review the Nostr protocol documentation for security considerations