# Nostr Mail Protocol

An email is an RFC 2822 message. Nostr Mail keeps the message and replaces the
transport: instead of travelling between SMTP servers, the message travels
inside a kind 1301 event, gift wrapped to its recipient.

---

## Specifications

| Spec | Covers |
|------|--------|
| [Nostr Mail Core](https://openspecs.uid.ovh/spec/npub1kg4sdvz3l4fr99n2jdz2vdxe2mpacva87hkdetv76ywacsfq5leqquw5te/nostr-mail-core) | The kind 1301 event, sending, bridging, signed rumors, public emails |
| [Nostr Mail Labels](https://openspecs.uid.ovh/spec/npub1kg4sdvz3l4fr99n2jdz2vdxe2mpacva87hkdetv76ywacsfq5leqquw5te/nostr-mail-labels) | Folders, read state, stars, custom tags |
| [Nostr Mail Settings](https://openspecs.uid.ovh/spec/npub1kg4sdvz3l4fr99n2jdz2vdxe2mpacva87hkdetv76ywacsfq5leqquw5te/nostr-mail-settings) | Public and private settings synced across devices |
| [Mail Contacts](https://github.com/nogringo/protocols/blob/main/mail-contacts.md) | A portable address book shared between mail clients |
| [Alias protocol](https://github.com/nogringo/protocols/blob/main/manage-nip05.md) | Claiming and releasing a `name@domain` on a NIP-05 domain |
| Bridges and DSN | Routing tags and delivery status notifications, below |
| Large MIME | Emails above the NIP-44 limit, stored on Blossom, below |

---

## Kind 1301: Email

```json
{
  "kind": 1301,
  "pubkey": "<sender_pubkey>",
  "tags": [
    ["email-id", "<unique_id>"]
  ],
  "content": "<rfc_2822_email>"
}
```

The content is a standard email. Nostr is only the delivery mechanism.

### Tags

| Tag | When | Description |
|-----|------|-------------|
| `email-id` | Recommended always | Unique random id generated by the sender, stable across edits of the event |
| `mail-from` | Bridged email | SMTP envelope sender |
| `rcpt-to` | Outbound bridging | SMTP envelope recipient, repeatable for Cc and Bcc |
| `public-ref` | Bcc of a public email | Id of the public event, followed by relay hints |

The `email-id` is what a bridge quotes in a delivery status notification, what a
client uses to thread replies, and what lets the same email be recognised across
devices and across relays.

---

## Sending

1. Build the kind 1301 rumor.
2. Gift wrap it ([NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md)).
3. Publish the wrap to the recipient's DM relays (kind 10050,
   [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md)).

Relays only ever see a kind 1059 wrap addressed to a pubkey. The sender, the
subject, the recipients and the body are all inside the encrypted payload.

### To a Nostr recipient

```json
{
  "kind": 1301,
  "pubkey": "<alice_pubkey>",
  "tags": [["email-id", "550e8400-e29b-41d4-a716-446655440000"]],
  "content": "From: npub1alice...@nostr\nTo: npub1bob...@nostr\nSubject: Hello\nDate: Sat, 28 Dec 2024 12:00:00 +0000\n\nHey Bob, how are you?"
}
```

### To a legacy recipient

A recipient who is not on Nostr is reached through a bridge. The wrap goes to
the bridge, and the envelope lives in the tags.

```json
{
  "kind": 1301,
  "pubkey": "<alice_pubkey>",
  "tags": [
    ["email-id", "550e8400-e29b-41d4-a716-446655440000"],
    ["mail-from", "npub1alice...@bridge.com"],
    ["rcpt-to", "bob@example.com"]
  ],
  "content": "<rfc_2822_email>"
}
```

Resolve the bridge pubkey with [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md)
on `_smtp@bridge_domain`.

### From a legacy sender

When a bridge receives inbound SMTP for a Nostr user, it builds the rumor on the
sender's behalf and sets `mail-from` so the recipient's client can tell a bridged
email from a native one. There is no `rcpt-to` inbound: the recipient is already
the `p` tag of the gift wrap.

```json
{
  "kind": 1301,
  "pubkey": "<bridge_pubkey>",
  "tags": [["mail-from", "alice@example.com"]],
  "content": "<rfc_2822_email>"
}
```

---

## Choosing the transport

An address of the form `name@domain` may belong to either world. The sender
decides, and never guesses:

1. Resolve `name@domain` through NIP-05. No pubkey means SMTP.
2. Read that pubkey's public settings from its NIP-65 write relays.
3. `prefer_nostr: true` means deliver over Nostr, anything else means SMTP.

A failed lookup counts as `false`. A transient NIP-05 error must never silently
route a Nostr recipient through a bridge.

---

## Authentication

The rumor is unsigned by default, which is what gives the sender deniability:
the recipient knows who wrote it, nobody else can prove it.

A sender who wants to prove authorship MAY sign the rumor. A signed rumor is a
complete Nostr event, so anyone can verify it, and it can be republished by
anyone without the recipient's cooperation. That is the useful property in a
professional or legal context.

---

## Public emails

An email to a public entity MAY skip the gift wrap and be published as a plain,
signed kind 1301 event. It is then a public and verifiable record that the
sender wrote it.

Bcc recipients of a public email are notified with a gift wrap whose rumor
carries a `public-ref` tag:

```json
["public-ref", "<public_event_id>", "wss://relay1.com", "wss://relay2.com"]
```

The client learns that the email is public and where to fetch the signed event.

---

## Large emails

NIP-44 caps a plaintext at 65535 bytes, and NIP-59 encrypts twice, so the usable
inline budget is smaller than it looks. Implementations keep MIME inline below a
safe threshold (the Dart SDK uses 32 KB) and move anything larger to a
[Blossom](https://github.com/hzrd149/blossom) server, encrypted with AES-GCM.
The kind 1301 event then carries the blob reference and its decryption key
instead of the message.

This is what lets Nostr Mail carry attachments of any size.

---

## Delivery status notifications

A bridge reports the fate of an email it was asked to send with a kind 7679
event referencing the `email-id` of the original. Delivered, delayed, failed,
queued: the sender's client learns what an SMTP user would read in a bounce
message.

The event carries no `p` tag, so the relays never learn who sent the original.
Its content is NIP-44 encrypted to the sender with an ephemeral key the bridge
destroys immediately, so the bridge cannot read its own archived notifications
either. The bridge's permanent key signs it, which is what makes it verifiable.

```json
{
  "kind": 7679,
  "pubkey": "<bridge_permanent_pubkey>",
  "tags": [
    ["r", "<email_id>"],
    ["ephemeral-pubkey", "<temp_pubkey>"]
  ],
  "content": "<nip44_encrypted_status>"
}
```

See [Delivery Status](/bridge/dsn/) for the full shape.

---

## Everything else a mailbox needs

Folders, read state and stars are [labels](https://openspecs.uid.ovh/spec/npub1kg4sdvz3l4fr99n2jdz2vdxe2mpacva87hkdetv76ywacsfq5leqquw5te/nostr-mail-labels):
signed kind 1985 events, each wrapped in its own gift wrap so the metadata stays
private, removed by deleting that wrap.

Signature, bridges and From identities are [private settings](https://openspecs.uid.ovh/spec/npub1kg4sdvz3l4fr99n2jdz2vdxe2mpacva87hkdetv76ywacsfq5leqquw5te/nostr-mail-settings):
a NIP-78 event encrypted to self. `dm_copy` and `prefer_nostr` are public,
because a bridge has to read them.

Contacts are an [append-only list](https://github.com/nogringo/protocols/blob/main/mail-contacts.md)
named `mail/contacts`, private by default, shared by every client that speaks
the same list.

An address like `alice@example.com` is claimed and released through the
[alias protocol](https://github.com/nogringo/protocols/blob/main/manage-nip05.md),
a small NIP-98 authenticated REST API served by the NIP-05 domain itself.

---

## NIPs used

| NIP | Usage |
|-----|-------|
| [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) | Event structure |
| [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) | `user@domain` resolution, bridge discovery |
| [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) | Deleting an email or a label |
| [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) | DM relays (kind 10050) |
| [NIP-32](https://github.com/nostr-protocol/nips/blob/master/32.md) | Labels (kind 1985) |
| [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) | Encryption |
| [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) | Gift wrap (kind 1059) |
| [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md) | Relay discovery |
| [NIP-78](https://github.com/nostr-protocol/nips/blob/master/78.md) | Settings (kind 30078) |
| [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) | Auth for the alias API |
