Sayan

Zero Knowledge Email aka zkEmails

Honestly, this whole thing started accidentally.

I was setting up signup and notification emails for a personal project. Nothing fancy, just the usual “thank you for signing up” stuff. I quickly ChatGPT-ed (yes, I use this term now) for a list of providers that support a free email-sending tier. I picked Brevo because they allow 300 emails per day on the free plan.

While wiring this up, I ran into a surprisingly annoying problem. I logged into the Brevo dashboard and created an SMTP credential. The UI asked me to copy the apiKey during creation, clearly stating it won’t be shown again. Fair enough that’s obviously the password.

But then, what’s the username?

I confidently assumed apiName would work. Copied it, configured everything, and started testing.

As you might have already guessed —> “Authentication Failed.”

Back to the dashboard. I stared at the UI again, looking for anything that resembled a username.

Time for some trial and error now!!.

openssl s_client -connect {brevo_smtp_host}:{port} -crlf -quiet
EHLO <custom-domain>.com # domain authenticated with Brevo
AUTH LOGIN
{username}
{password}
QUIT

Eventually, I discovered that a gnarly string like
<identifier>@smtp-brevo.com labeled Login in the UI worked perfectly. Now that I’m writing this and looking back at the UI, I feel stupid.
I don’t know why it did not click immediately. Anyway, reconfigured everything and it worked like a charm. Phew. Work done. Went out for dinner.


An Array of Fleeting Ideas

While sitting in the cab, I was reading through some zero-knowledge articles and papers I had collected during a math course on elliptic curves. The first few lectures were pure “WTF is happening?” but then gradually things started to make sense.

Well let’s reserve the learnings of that for another post. However, in course of that an obvious question hit me.

Why don’t we have end-to-end encryption for email? I know some of you folks will point me to ProtonMail but bear with me :)

More specifically:

  • Why doesn’t my Gmail have E2E encryption?
  • Why should Gmail read everything?
  • And why have we collectively accepted this for decades?

To make matters worse, Sergey is back, Gemini is being pushed hard, and let’s be real, that means even more of our emails will be read, indexed, and mined. “Knock Knock”, Privacy who?

So I decided to build something for myself.


Grabbing the toolkit for some under the hood fun

I came back home and started tinkering around SMTP and IMAP. I wrote small snippets of code to:

  • manually send emails using SMTP
  • list messages using IMAP
  • inspect headers
  • see how providers actually store and expose messages

After a few hours of talking loudly to myself, ChatGpting and debugging, I had a working prototype.

Introducing zkEmails (because why not?)

Table of Contents

  1. What is ZKE?
  2. Prerequisites
  3. Installation
  4. Gmail App Password Setup
  5. Getting Started
  6. How It Works
  7. Troubleshooting

What is ZKE?

ZKE (Zero Knowledge Emails) is a privacy-focused email client that adds end-to-end encryption to your existing Gmail account. Your messages are encrypted on your device before being sent, and only the intended recipient can decrypt them.

Key Features:

  • Works with your existing Gmail account
  • End-to-end encryption (only you and your recipient can read messages)
  • Both GUI and command-line interfaces available
  • Multi-profile support for multiple email accounts
  • No servers involved - encryption happens locally

Prerequisites

Required

  • Java 17 or higher - The installer will help you install this if needed
  • Gmail account with 2-Factor Authentication enabled
  • Gmail App Password (not your regular Gmail password)

Supported Platforms

  • macOS (Intel and Apple Silicon)
  • Linux (Ubuntu, Debian, Fedora, etc.)
  • Windows (via WSL)

Installation

Open your terminal and run:

curl -fsSL https://raw.githubusercontent.com/unlimited91/zkemails/1.0.0.beta2/install.sh | bash

This will:

  1. Check for Java 17+ (and offer to install via SDKMAN if missing)
  2. Download the latest ZKE release
  3. Create the zke command in your PATH

After installation, restart your terminal or run:

source ~/.zshrc    # for zsh
source ~/.bashrc   # for bash

Verify Installation

zke --help

You should see the help menu with available commands.

Manual Install

If you prefer to install manually:

  1. Download the installer script:
    curl -fsSL https://raw.githubusercontent.com/unlimited91/zkemails/1.0.0.beta2/install.sh -o install.sh
    chmod +x install.sh
    
  2. Run the installer:
    ./install.sh
    
  3. Or install a specific version:
    ./install.sh -v 1.0.0.beta2
    

Gmail App Password Setup

Important: ZKE requires a Gmail App Password, not your regular Gmail password.

Step 1: Enable 2-Factor Authentication

  1. Go to Google Account Security
  2. Under “How you sign in to Google”, click 2-Step Verification
  3. Follow the prompts to enable 2FA if not already enabled

Step 2: Create an App Password

  1. Go to App Passwords
    • If you don’t see this option, 2FA may not be enabled
  2. Click Select app and choose “Mail”
  3. Click Select device and choose “Other (Custom name)”
  4. Enter “ZKE” as the name
  5. Click Generate
  6. Copy the 16-character password (looks like: xxxx xxxx xxxx xxxx)
  7. Remove the spaces when using it in ZKE

Keep this password safe! You’ll need it to initialize ZKE.


Getting Started

Launch the graphical interface:

zke gui

The GUI provides:

  • Inbox view with encrypted messages
  • Compose new encrypted messages
  • Manage contacts and invites
  • Multi-profile support

Option 2: CLI Mode (For terminal lovers)

Initialize Your Profile

zke init --email your.email@gmail.com

You’ll be prompted for your Gmail App Password.

Invite a Contact

Before you can exchange encrypted messages, you need to exchange keys with your contact:

zke invite --to friend@gmail.com

This sends an invitation email. Once they accept, you can exchange encrypted messages.

Accept an Invite

When someone invites you:

# List pending invites
zke lsi

# Accept an invite
zke ack invi --invite-id <INVITE-UUID>

Sync Keys

After your contact accepts your invite, sync their public key:

zke sync-ack

Send Encrypted Messages

# Opens an editor to compose
zke sem --to friend@gmail.com

# Or specify subject
zke sem --to friend@gmail.com --subject "Secret plans"

Read Encrypted Messages

# List encrypted messages
zke rem

# Read a specific message
zke rem --message 42

# View entire conversation thread
zke rem --thread 42

# Reply to a message
zke rem --reply 42

How It Works

Key Exchange (TOFU - Trust On First Use)

  1. You send an invite - ZKE generates a unique key pair and sends your public key
  2. They accept - They generate their keys and send their public key back
  3. Keys are exchanged - Now you can encrypt messages for each other

Encryption

  • Messages are encrypted using your contact’s public key
  • Only they can decrypt it with their private key
  • Your private key never leaves your device

Local Storage

All data is stored locally in ~/.zkemails/:

  • config.json - Email server configuration
  • keys.json - Your encryption keys (keep private!)
  • contacts.json - Public keys of your contacts
  • inbox/ - Cached encrypted messages
  • outbox/ - Sent messages

Troubleshooting

“Java not found” error

The installer should offer to install Java via SDKMAN. If it fails:

# Install SDKMAN
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

# Install Java 17
sdk install java 17-tem

“Authentication failed” error

  1. Make sure you’re using a Gmail App Password, not your regular password
  2. Verify 2FA is enabled on your Google account
  3. Try generating a new App Password

“Connection refused” error

Check your internet connection and firewall settings. ZKE needs access to:

  • imap.gmail.com:993 (reading emails)
  • smtp.gmail.com:587 (sending emails)

GUI doesn’t start

Make sure you have Java 17+ with JavaFX support:

java -version

If using SDKMAN, install a JDK with JavaFX:

sdk install java 17.0.9-zulu

Messages not decrypting

  • Verify the sender is in your contacts: zke lsc
  • Make sure you’ve completed the key exchange: zke sync-ack

Need more help?

  • Check logs in ~/.zkemails/logs/
  • Run commands with --verbose flag for more details
  • Open an issue at GitHub Issues

Command Reference

Command Description
zke gui Launch graphical interface
zke init Initialize with your email
zke invite Send invite to start encrypted chat
zke sem Send encrypted message
zke rem Read encrypted messages
zke lsi List pending invites
zke ack invi Accept an invite
zke sync-ack Sync keys from accepted invites
zke lsc List contacts
zke lsp List profiles
zke pset Switch active profile

Use zke <command> --help for detailed options.


Uninstall

To remove ZKE:

rm -rf ~/.zkemails

Remove the PATH entry from your ~/.zshrc or ~/.bashrc if desired.


Questions or feedback? Open an issue at https://github.com/unlimited91/zkemails/issues