Skip to content

HardDrivenHolo/HoloMail

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

HoloMail

A minimal but fully-working Holochain “email” hApp that lets two agents exchange end-to-end encrypted messages with no central server.
It is split into four parts:

  1. DNA bundle (zomes + integrity)
  2. UI (vanilla JS / Web-Components, works in any browser once you have the Holochain Launcher)
  3. Packaging & running
  4. Next steps / hardening checklist

You can copy-paste everything into a new folder and have a chat-like “decentralised email” running on your laptop in ≈ 10 min.


1. DNA – dnas/mail/workdir

dna.yaml

---
manifest_version: "1"
name: holo_mail
integrity:
  zomes:
    - name: mail_integrity
      bundled: ../target/wasm32-unknown-unknown/release/mail_integrity.wasm
coordinator:
  zomes:
    - name: mail
      bundled: ../target/wasm32-unknown-unknown/release/mail.wasm
      dependencies:
        - name: mail_integrity

zomes/mail_integrity/src/lib.rs

use hdi::prelude::*;

#[hdk_entry_defs]
#[unit_enum(UnitEntryTypes)]
pub enum EntryTypes {
    #[entry_def(required_validations = 5)]
    Message(Message),
}

#[hdk_entry_helper]
#[serde(rename_all = "camelCase")]
pub struct Message {
    pub to: AgentPubKey,
    pub cipher_text: String,          // base64 encrypted body
    pub nonce: String,                // base64 nonce
    pub created_at: Timestamp,
}

#[hdk_link_types]
pub enum LinkTypes {
    Sent,
    Inbox,
}

zomes/mail/src/lib.rs

use hdk::prelude::*;
use mail_integrity::*;

#[hdk_extern]
pub fn send_message(input: SendMessageInput) -> ExternResult<ActionHash> {
    let message = Message {
        to: input.to,
        cipher_text: input.cipher_text,
        nonce: input.nonce,
        created_at: sys_time()?,
    };
    let ah = create_entry(&EntryTypes::Message(message.clone()))?;

    // link from my “sent” list
    create_link(
        agent_info()?.agent_latest_pubkey,
        ah.clone(),
        LinkTypes::Sent,
        (),
    )?;
    // link to recipient’s “inbox”
    create_link(
        input.to,
        ah.clone(),
        LinkTypes::Inbox,
        (),
    )?;
    Ok(ah)
}

#[hdk_extern]
pub fn get_inbox(_: ()) -> ExternResult<Vec<Record>> {
    let links = get_links(
        agent_info()?.agent_latest_pubkey,
        LinkTypes::Inbox,
        None,
    )?;
    let records = links
        .into_iter()
        .filter_map(|l| get(ActionHash::from(l.target), GetOptions::default()).ok()?)
        .collect();
    Ok(records)
}

#[hdk_extern]
pub fn get_sent(_: ()) -> ExternResult<Vec<Record>> {
    let links = get_links(
        agent_info()?.agent_latest_pubkey,
        LinkTypes::Sent,
        None,
    )?;
    let records = links
        .into_iter()
        .filter_map(|l| get(ActionHash::from(l.target), GetOptions::default()).ok()?)
        .collect();
    Ok(records)
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SendMessageInput {
    to: AgentPubKey,
    cipher_text: String,
    nonce: String,
}

2. Browser UI – ui/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>Holochain Mail</title>
    <script type="module">
      import { AppWebsocket, encodeHashToBase64 } from
        'https://unpkg.com/@holochain/client@0.16.0/dist/index.es.js';
      import * as nacl from
        'https://unpkg.com/tweetnacl@1.0.3/nacl-fast.js';

      const client = await AppWebsocket.connect();
      const appInfo = await client.appInfo();
      const cell = appInfo.cell_info.mails[0].provisioned;

      // ----- helpers -----
      const b642buf = b64 => Uint8Array.from(atob(b64), c => c.charCodeAt(0));
      const buf2b64 = buf => btoa(String.fromCharCode(...buf));

      document.getElementById('sendBtn').addEventListener('click', async () => {
        const toStr = document.getElementById('to').value.trim();
        const plaintext = document.getElementById('body').value.trim();
        const to = { type: 'AgentPubKey', data: b642buf(toStr) };

        // ephemeral keypair for E2E
        const boxKp = nacl.box.keyPair();
        const nonce = nacl.randomBytes(24);
        const cipher = nacl.box(
          new TextEncoder().encode(plaintext),
          nonce,
          b642buf(toStr),
          boxKp.secretKey
        );

        await client.callZome({
          cell_id: cell.cell_id,
          zome_name: 'mail',
          fn_name: 'send_message',
          payload: {
            to,
            cipher_text: buf2b64(cipher),
            nonce: buf2b64(nonce),
          }
        });
        alert('sent!');
      });

      async function refresh() {
        const inbox = await client.callZome({
          cell_id: cell.cell_id,
          zome_name: 'mail',
          fn_name: 'get_inbox',
          payload: null
        });
        const list = document.getElementById('inbox');
        list.innerHTML = '';
        inbox.forEach(r => {
          const li = document.createElement('li');
          li.textContent = JSON.stringify(r.entry);
          list.appendChild(li);
        });
      }
      setInterval(refresh, 5000);
    </script>
  </head>
  <body>
    <h1>Holochain Mail</h1>
    <label>To (AgentPubKey base64): <input id="to" size="64"/></label><br/>
    <label>Message:<br/><textarea id="body" rows="4" cols="60"></textarea></label><br/>
    <button id="sendBtn">Send</button>
    <h2>Inbox (auto-refresh)</h2>
    <ul id="inbox"></ul>
  </body>
</html>

UI uses TweetNaCl for fast E2E encryption but you can swap in any library.
The recipient’s browser needs the secret key corresponding to the public key used in the box.


3. Build & Run

Install tools

# 1) Holochain dev environment
curl https://holochain.github.io/holochain/install.sh | sh
# 2) Rust nightly
rustup target add wasm32-unknown-unknown

Build DNA

cd dnas/mail
cargo build --release --target wasm32-unknown-unknown

Package & run conductor in dev mode

nix-shell https://holochain.love  # or use your cargo-installed hc
hc dna pack workdir
hc app pack workdir
hc sandbox generate workdir/holo_mail.happ --run-dna

This spins up two agents on localhost:8888 and 8889; open two browser tabs to ui/index.html (served via any static server) and send messages between them.


4. Next Steps / Production Hardening

Feature How to add
Rich metadata Extend Message struct: subject, attachments (IPFS CIDs), threading hash
Multi-device identity Use DeepKey hApp for key rotation
Group mail / lists Create membranes (“private neighbourhoods”) with Neighbourhoods framework
Relay when offline Publish via Holo Hosting or let friends run “mail-relay” hApp cells
Spam filtering Attach web-of-trust links (Trust Graph) to rate-limit unknown senders
Portable UI Wrap UI with Tauri or Electron; ship as standalone desktop app

5. One-liner TL;DR

git clone <this-repo> && cd holo-mail && nix develop --command "hc-spin up" → two agents can now send censorship-resistant, end-to-end encrypted e-mail with no servers in under a minute.

About

A minimal but fully-working Holochain “email” hApp that lets two agents exchange end-to-end encrypted messages. This spins up two agents on localhost:8888 and 8889; open two browser tabs to ui/index.html (served via any static server) and send messages between them.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors