Sigil Mail is an on-chain delivery substrate for sealed envelopes. The chain stores commitments and policy; the payload itself lives off-chain (Weave or IPFS). Recipients hold a long-term delivery key whose public half they publish on-chain. The Rust SDK exposes the helpers needed to derive mailbox ids, build delivery commitments, and validate payloads before signing. See mail.rs.

When to use this

  • Send a notification, invoice, or signed document to another DID.
  • Build an inbox or DM-style interface.
  • Issue webhooks where the recipient controls a public key, not a URL.

Prerequisites

  • Sender funded DID.
  • Recipient must have published a delivery key.
  • An off-chain content store (default: Weave) to host the encrypted payload.

Recipe

1

Publish your delivery key (recipients)

Anyone receiving mail first publishes the public half of their delivery key. The chain accepts X25519 keys for AEAD-based payload encryption.
await signAndSend({
  SigilMailPublishKey: {
    owner_did: process.env.SIGIL_SENDER_DID,
    public_key: publicKeyHex,             // 32-byte X25519 pubkey, hex
    algorithm: 'x25519-xchacha20-poly1305',
    expires_at_height: null,              // null = no expiry
  },
});
Look it up with:
curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_getMailKeys",
       "params":{"owner_did":"did:oas:sigil:agent:..."}}'
2

Set a delivery policy (optional)

Restrict who may send to your mailbox. Common policies: open, allowlist, stake-gated, or controller-only.
await signAndSend({
  SigilMailSetPolicy: {
    owner_did: process.env.SIGIL_SENDER_DID,
    policy: { Allowlist: { senders: ['did:oas:sigil:agent:trusted1...'] } },
  },
});
The Rust SDK’s allow_all_policy() helper returns the canonical permissive policy.
3

Send mail

The sender:
  1. Encrypts the payload off-chain with the recipient’s delivery key.
  2. Pins the ciphertext (Weave, IPFS, S3, …) and gets a content address.
  3. Computes the delivery commitment.
  4. Submits a SigilMailSend transaction.
The Rust SDK exposes preflight_delivery_commitment and delivery_commitment so you can validate the commitment locally before submitting. Cross-language pattern:
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex } from '@noble/ciphers/utils';

function deliveryCommitment(input: {
  senderDid: string;
  recipientDid: string;
  mailboxId: string;
  ciphertextCid: string;
  ciphertextHash: string;        // hex of BLAKE3 over ciphertext
  nonce: string;                 // 24-byte XChaCha nonce, hex
}): string {
  const enc = new TextEncoder();
  const parts = [
    'sigil/mail/v1\n',
    input.senderDid, '|',
    input.recipientDid, '|',
    input.mailboxId, '|',
    input.ciphertextCid, '|',
    input.ciphertextHash, '|',
    input.nonce,
  ].map((s) => enc.encode(s));
  const total = parts.reduce((sum, p) => sum + p.length, 0);
  const buf = new Uint8Array(total);
  let offset = 0;
  for (const part of parts) { buf.set(part, offset); offset += part.length; }
  return bytesToHex(blake3(buf));
}

await signAndSend({
  SigilMailSend: {
    recipient_did: 'did:oas:sigil:agent:recipient...',
    mailbox_id: 'mb:alice/inbox',
    ciphertext_cid: 'bafkrei...',
    ciphertext_hash: ciphertextBlake3Hex,
    nonce: nonceHex,
    commitment,                       // value from deliveryCommitment(...)
    size_bytes: 4096,
    memo: null,
  },
});
4

Resolve a delivery

The recipient queries sigil_resolveMail and decrypts off-chain.
curl -s "$SIGIL_RPC_URL" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"sigil_resolveMail",
       "params":{"commitment":"..."}}'

Common errors

SymptomCauseFix
recipient has no delivery keyRecipient never called SigilMailPublishKeyHave the recipient publish their key first
policy denies senderAllowlist mismatchAsk the recipient to update their policy
commitment mismatchLocal commitment ≠ recomputed valueRe-run preflight; check field order and nonce bytes
ciphertext too largePer-mail size cap exceededSplit into multiple deliveries or use a chunked transport

See also