Scheduler DVM
NIP Scheduler DVM
draft optional
This NIP defines a privacy-preserving protocol for scheduling Nostr events to be published at a future time, using a Data Vending Machine (NIP-90) as the scheduling backend.
Motivation
Publishing a Nostr event at a future time requires an always-online server. This NIP standardizes the interaction between a client and a Scheduler DVM so that:
- Any client can schedule any signed event without implementing its own server
- Multiple Scheduler DVMs can exist and compete (self-hostable, open market)
- No observer or relay can link a scheduled job to its future published event
- No observer or relay can read the content or the target publication time of a scheduled job
- The DVM operator can read the scheduled event content and publication time after decryption
Roles
- Client: the Nostr user who wants to schedule an event
- Scheduler DVM: an always-online service that publishes the signed event at the requested time
Kinds
| Kind | Description |
|---|---|
5905 | Schedule job request (client -> DVM) |
7000 | Job feedback (DVM -> client) |
5 | Delete / cancel a job (standard Nostr delete) |
1. Creating a Schedule
The client publishes a kind:5905 job request:
{
"kind": 5905,
"pubkey": "<client_pubkey>",
"content": "<nip44_encrypt(dvm_pubkey, client_privkey, payload)>",
"tags": [
["p", "<dvm_pubkey>"],
["encrypted"]
]
}Encrypted Payload
{
"job_id": "<64 random hex chars>",
"schedule_at": 1748000000,
"signed_event": { "<full signed Nostr event>" },
"relays": ["wss://relay.damus.io", "wss://nos.lol"]
}| Field | Type | Required | Description |
|---|---|---|---|
job_id | string | yes | 64-char random hex, generated by the client. Stable identifier for this job. |
schedule_at | unix timestamp | yes | UTC time at which the DVM should publish the event |
signed_event | object | yes | The complete, already-signed Nostr event to publish. |
relays | array of strings | yes | Relay URLs where the DVM should publish the event |
2. Cancelling a Schedule
The client publishes a standard NIP-09 delete event tagging the kind:5905 to cancel:
{
"kind": 5,
"tags": [
["e", "<id of the kind:5905 event to cancel>"]
]
}The DVM MUST send a kind:7000 feedback with status:cancelled after processing the deletion.
3. Listing Schedules
The client fetches its own job requests directly from relays:
{
"authors": ["<client_pubkey>"],
"kinds": [5905]
}The client decrypts each event's content locally to reconstruct the list of scheduled jobs and their job_ids.
4. Job Feedback (DVM -> Client)
The DVM sends status updates using kind:7000 with no p tag to avoid linking the feedback to the client's pubkey. The content is encrypted using a one-time ephemeral keypair that the DVM destroys immediately after use.
{
"kind": 7000,
"pubkey": "<dvm_pubkey>",
"content": "<nip44_encrypt(ephemeral_privkey, client_pubkey, feedback_payload)>",
"tags": [
["r", "<job_id>"],
["ephemeral-pubkey", "<ephemeral_pubkey>"]
]
}Feedback Payload (decrypted content)
{
"status": "scheduled",
"message": "<optional human-readable string>"
}| Status | Description |
|---|---|
scheduled | DVM has accepted and queued the job |
published | The event has been broadcast to the requested relays |
failed | All relays rejected or were unreachable |
cancelled | The job was cancelled via kind:5 |
error | The job request was invalid |
5. DVM Discoverability (NIP-89)
Scheduler DVMs SHOULD announce themselves using NIP-89:
{
"kind": 31990,
"pubkey": "<dvm_pubkey>",
"content": "{\"name\": \"My Scheduler DVM\", \"about\": \"Schedule any Nostr event.\"}",
"tags": [
["k", "5905"],
["t", "scheduler"]
]
}