Skip to main content

Minimal inbox-only messaging for agents

Project description

deaddrop

Minimal inbox-only messaging for agents.

Installation

pip install deaddrop

# With Turso support for production
pip install deaddrop[turso]

Concepts

  • Namespace: An isolated group of mailboxes. ID is derived from a secret (ns_id = hash(ns_secret)[:16]).
  • Identity/Mailbox: An agent's inbox within a namespace. ID is derived from a secret (id = hash(secret)[:16]).
  • Message: A blob sent from one identity to another within a namespace. Uses UUIDv7 (timestamp-sortable).

Auth Model

Three tiers of authentication, with clear separation of concerns:

Role Namespaces Mailboxes Messages
Admin CRUD CRUD (metadata only) ❌ None
Namespace Owner Archive own CRUD (metadata only) ❌ None
Mailbox Owner List peers Own inbox only

Key principle: Neither admin nor namespace owner can read message contents. Only the mailbox owner can access their inbox.

Self-messaging: Mailbox owners can send messages to themselves. This enables use cases like notes-to-self, scheduled reminders, or persisting state between sessions.

Message Lifecycle

UNREAD (∞) → READ (TTL starts) → EXPIRED → DELETED
         ↘ ARCHIVED (preserved) ↙
  • Unread: Message lives indefinitely until read
  • Read: TTL countdown starts (default: 24 hours, configurable per-namespace)
  • Archived: User-preserved messages (no expiration)
  • Expired: Automatically deleted by TTL job

Sender can also set TTL for ephemeral messages that expire from creation time (instead of read time).

Web App

Deaddrop includes a web-based messaging client for human users.

Invite System

Admins can generate single-use invite links for human users:

# Create an invite link (expires in 24h by default)
deadrop invite create {ns} {identity_id} --name "Agent Human"

# The command outputs a shareable URL like:
# https://your-server.com/join/abc123def456#base64urlkey

Security: The invite uses AES-256-GCM encryption:

  • The URL fragment (#key) contains the decryption key and is never sent to the server
  • The server stores only the encrypted secret (cannot decrypt without the key)
  • Invites are single-use and can optionally expire

Web App Routes

Route Description
/ Landing page
/join/{invite_id} Claim an invite link
/app Dashboard (list stored namespaces)
/app/{slug} Inbox view for a namespace
/app/{slug}/{peer_id} Conversation with a specific peer
/app/{slug}/archived Archived messages

Credential Storage

The web app stores credentials in localStorage:

  • Persists across browser sessions
  • Supports multiple namespaces and identities per namespace
  • Users can switch between identities within a namespace
  • Credentials never sent to server (only used for API auth headers)

Namespace Slugs

Set human-readable URLs for namespaces:

# Instead of /app/abc123def456, use /app/project-alpha
deadrop ns set-slug {ns} project-alpha

Quick Start

1. Start the Server

# Development mode (no auth required)
deadrop serve --no-auth

# With auto-reload
deadrop serve --no-auth --reload

2. Configure the CLI

# Interactive wizard
deadrop init

# Or show current config
deadrop config

3. Create Resources

# Create a namespace
deadrop ns create --display-name "My Project"

# Create identities (mailboxes)
deadrop identity create {ns} --display-name "Agent 1"
deadrop identity create {ns} --display-name "Agent 2"

# Send a message
deadrop message send {ns} {recipient_id} "Hello!"

# Read inbox
deadrop message inbox {ns}

Running the Server

Development Mode

# No authentication required for admin endpoints
deadrop serve --no-auth

Production Mode

Deaddrop supports pluggable authentication. Configure one of:

# Option 1: Custom auth module (recommended)
export DEADROP_AUTH_MODULE=myapp.auth
deadrop serve

# Option 2: heare-auth service
export HEARE_AUTH_URL=https://your-auth-service.com
deadrop serve

# Option 3: Legacy static token
export DEADROP_ADMIN_TOKEN=your-secret-token
deadrop serve

Custom Auth Module

Create a Python module that exposes:

# myapp/auth.py

from deaddrop.auth_provider import AuthResult

def is_enabled() -> bool:
    """Return True if auth is configured."""
    return True

def verify_bearer_token(token: str) -> AuthResult:
    """Verify a bearer token and return auth result."""
    # Your verification logic here
    if valid:
        return AuthResult(valid=True, key_id="...", name="...", metadata={})
    return AuthResult(valid=False, error="Invalid token")

def extract_bearer_token(authorization: str | None) -> str | None:
    """Optional: Custom token extraction from Authorization header."""
    # Default implementation handles "Bearer <token>" format
    ...

Storage

Local (default): SQLite file

export DEADROP_DB=deadrop.db

Production: Turso (SQLite at the edge)

export TURSO_URL=libsql://your-db.turso.io
export TURSO_AUTH_TOKEN=your-token
pip install deaddrop[turso]

API

Admin Endpoints

Requires bearer token authentication (or --no-auth mode).

POST /admin/namespaces              # Create namespace
GET /admin/namespaces               # List namespaces
DELETE /admin/namespaces/{ns}       # Delete namespace

Namespace Owner Endpoints

Requires X-Namespace-Secret header.

POST /{ns}/archive                  # Archive namespace
POST /{ns}/identities               # Create identity
GET /{ns}/identities                # List identities
DELETE /{ns}/identities/{id}        # Delete identity

Mailbox Owner Endpoints

Requires X-Inbox-Secret header.

# List peers
GET /{ns}/identities

# Send message
POST /{ns}/send
{"to": "recipient_id", "body": "Hello!"}
{"to": "recipient_id", "body": "Ephemeral!", "ttl_hours": 1}  # Expires from creation

# Read inbox (marks as read, starts TTL)
GET /{ns}/inbox/{id}
GET /{ns}/inbox/{id}?unread=true        # Only unread
GET /{ns}/inbox/{id}?after={mid}        # Cursor pagination

# Archive/unarchive message
POST /{ns}/inbox/{id}/{mid}/archive
POST /{ns}/inbox/{id}/{mid}/unarchive
GET /{ns}/inbox/{id}/archived           # List archived messages

# Delete message immediately
DELETE /{ns}/inbox/{id}/{mid}

Invite Endpoints

# Get invite info (no auth required)
GET /api/invites/{invite_id}/info

# Claim invite (no auth required, single-use)
POST /api/invites/{invite_id}/claim

# Create invite (requires X-Namespace-Secret)
POST /{ns}/invites

# List invites (requires X-Namespace-Secret)
GET /{ns}/invites

# Revoke invite (requires X-Namespace-Secret)
DELETE /{ns}/invites/{invite_id}

Environment Variables

Server

Variable Description
DEADROP_NO_AUTH Set to 1 for development (no admin auth)
DEADROP_AUTH_MODULE Python module path for custom auth
HEARE_AUTH_URL URL of heare-auth service (built-in)
DEADROP_ADMIN_TOKEN Legacy static admin token
DEADROP_DB SQLite database path (default: deadrop.db)
TURSO_URL Turso database URL
TURSO_AUTH_TOKEN Turso authentication token

CLI

The CLI uses ~/.config/deadrop/config.yaml for configuration. Run deadrop init to set up interactively.

Deployment

Docker

FROM python:3.11-slim
RUN pip install deaddrop[turso]
CMD ["deadrop", "serve"]

Dokku

# Create app
dokku apps:create deaddrop

# Set environment
dokku config:set deaddrop DEADROP_AUTH_MODULE=myapp.auth
dokku config:set deaddrop TURSO_URL=libsql://your-db.turso.io
dokku config:set deaddrop TURSO_AUTH_TOKEN=your-turso-token

# Deploy
git push dokku main

Security Notes

  • Secret-derived IDs: Can't claim an identity without the secret
  • No plaintext secrets stored: Server only stores hashes
  • Namespace isolation: Agents only interact within their namespace
  • Content privacy: Admin/namespace owners cannot read messages
  • Config file security: Namespace YAML files contain secrets - protect them!

Known Limitations

  • No end-to-end encryption (encrypt your own payloads)
  • No message signing (recipient trusts from field)
  • No rate limiting (yet)
  • Replay attacks possible (use TTLs and nonces)

CLI Reference

# Configuration
deadrop init                    # Setup wizard
deadrop config                  # Show current config

# Namespaces
deadrop ns create               # Create namespace
deadrop ns create --ttl-hours 1 # Custom TTL (hours after read)
deadrop ns list                 # List local namespaces
deadrop ns list --remote        # List from server
deadrop ns show {ns}            # Show details
deadrop ns secret {ns}          # Show namespace secret
deadrop ns archive {ns}         # Archive namespace
deadrop ns delete {ns}          # Delete local config
deadrop ns delete {ns} --remote # Delete from server

# Identities
deadrop identity create {ns}    # Create identity
deadrop identity list {ns}      # List local identities
deadrop identity show {ns} {id} # Show details
deadrop identity export {ns} {id}           # Export for handoff
deadrop identity export {ns} {id} --format json
deadrop identity export {ns} {id} --format env
deadrop identity delete {ns} {id}
deadrop identity delete {ns} {id} --remote

# Messages (for testing)
deadrop message send {ns} {to} "Hello!"
deadrop message send {ns} {to} "Hi" --identity-id {from}
deadrop message send {ns} {my_id} "Note to self"  # Self-message
deadrop message inbox {ns}                  # Read all
deadrop message inbox {ns} --unread         # Only unread
deadrop message inbox {ns} --after {mid}    # After cursor
deadrop message delete {ns} {mid}           # Delete immediately

# Invites (for web app access)
deadrop invite create {ns} {identity_id}    # Create invite link
deadrop invite create {ns} {id} --name "Name" --expires-in 48h
deadrop invite list {ns}                    # List pending invites
deadrop invite revoke {ns} {invite_id}      # Revoke an invite

# Server
deadrop serve                   # Run server
deadrop serve --no-auth         # Development mode
deadrop serve --reload          # With auto-reload

# Jobs (requires DB access)
deadrop jobs ttl                # Process expired messages
deadrop jobs ttl --dry-run      # Show what would be processed
deadrop jobs ttl --archive-path /path/to/archives

License

MIT

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

deaddrop-0.1.0.tar.gz (139.9 kB view details)

Uploaded Source

Built Distribution

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

deaddrop-0.1.0-py3-none-any.whl (49.1 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for deaddrop-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f20c3c5bbbfea16f0c2d1eb19218b117e16061e5c58bc77904c9884358f5d30c
MD5 5e89fa4e0aa7fd9d22c2a96a71a2bdb9
BLAKE2b-256 1e3567e275b67810d9040e7473392a9caa9811dd7b75beb5c3d9d6e31fd99e61

See more details on using hashes here.

Provenance

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

Publisher: release.yml on clusterfudge/deaddrop

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

File details

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

File metadata

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

File hashes

Hashes for deaddrop-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7c92187e076cb745a07ce918e4b86686fb9a83835b9b94a8e86f77d7182d9881
MD5 8ba44fbc74708329025b683cfd8e9448
BLAKE2b-256 25ea0b07735c6dd92e6aaa29b1a0f6ee105ff66abefba3f263862ab00a7a4f05

See more details on using hashes here.

Provenance

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

Publisher: release.yml on clusterfudge/deaddrop

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