Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# backend

The `backend` package defines a unified `Provider` interface for multi-protocol email support and provides three protocol implementations: IMAP, JMAP, and POP3.

## Architecture

This package acts as an abstraction layer, allowing the rest of the application to interact with mail servers through a consistent interface regardless of the underlying protocol. Implementations self-register at init time via `RegisterBackend`, and the factory function `New()` creates the right provider based on the account's `Protocol` field (defaults to `"imap"`).

### Provider interface

The `Provider` interface composes five sub-interfaces:

| Interface | Methods | Purpose |
|-----------|---------|---------|
| `EmailReader` | `FetchEmails`, `FetchEmailBody`, `FetchAttachment` | Retrieve email lists, bodies, and raw attachments |
| `EmailWriter` | `MarkAsRead`, `DeleteEmail`, `ArchiveEmail`, `MoveEmail` | Modify email state and location |
| `EmailSender` | `SendEmail` | Send outgoing mail |
| `FolderManager` | `FetchFolders` | List available mailboxes |
| `Notifier` | `Watch` | Real-time push notifications for mailbox changes |

Backends that don't support an operation return `ErrNotSupported`.

## Protocols

### IMAP (`backend/imap`)

Wraps the existing `fetcher` and `sender` packages behind the `Provider` interface. IMAP IDLE is handled externally in `main.go`, so `Watch()` returns `ErrNotSupported`.

### JMAP (`backend/jmap`)

Native JMAP implementation (RFC 8620 / RFC 8621) using `go-jmap`. Supports OAuth2 and Basic Auth, real-time push via JMAP EventSource, and full mailbox operations including send (via `EmailSubmission`). JMAP string IDs are hashed to `uint32` UIDs for interface compatibility.

### POP3 (`backend/pop3`)

POP3 + SMTP implementation. Inherently limited to a single INBOX folder, no read flags, no move/archive, and no push notifications. Uses the `sender` package for outgoing mail.

## Files

| File | Description |
|------|-------------|
| `backend.go` | Core interfaces and data types (`Provider`, `Email`, `Attachment`, `Folder`, `OutgoingEmail`, `NotifyEvent`, `Capabilities`) |
| `factory.go` | Protocol registry and `New()` factory function |
| `imap/imap.go` | IMAP provider — adapter over `fetcher` and `sender` packages |
| `jmap/jmap.go` | JMAP provider — native implementation with session management and mailbox caching |
| `pop3/pop3.go` | POP3 provider — per-connection model with UIDL-based UID hashing |
26 changes: 26 additions & 0 deletions clib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ Image decoding and PNG re-encoding using [stb_image](https://github.com/nothings

Files: `imgconv.c`, `imgconv.h`, `imgconv.go`, `stb_image.h`, `stb_image_write.h`

### htmlconv

Single-pass HTML-to-structured-elements parser. Takes raw HTML and returns a slice of `HTMLElement` values representing headings, links, images, blockquotes, tables, and text. Used by the email view to render HTML emails in the terminal without a full DOM.

- `HTMLToElements()` — parses HTML into structured elements with type, text, and up to two attributes (e.g., `href`/`src`, `alt`/`cite`).

Files: `htmlconv.c`, `htmlconv.h`, `htmlconv.go`

### markdown

Markdown-to-HTML conversion using [md4c](https://github.com/mity/md4c) (vendored). Supports GitHub-flavored features: tables, strikethrough, task lists, and permissive autolinks.

- `MarkdownToHTML()` — converts Markdown bytes to HTML bytes.

Files: `md4c.c`, `md4c.h`, `md4c-html.c`, `md4c-html.h`, `markdown.go`

## Pure Go fallbacks

Every function has a `_nocgo.go` counterpart (build tag `!cgo`) that provides the same API using pure Go libraries:

| C implementation | Go fallback |
|-----------------|-------------|
| `base64wrap.go` | Manual string builder |
| `imgconv.go` (stb_image) | `image/png`, `image/jpeg`, `image/gif` |
| `htmlconv.go` | `goquery` DOM parsing |
| `markdown.go` (md4c) | `goldmark` |

## Adding new C code

Expand Down
16 changes: 16 additions & 0 deletions config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@ All cache files use JSON serialization with restrictive file permissions (`0600`
| `cache.go` | Email, contacts, and drafts caching. Provides CRUD operations for `EmailCache`, `ContactsCache` (with search and frequency-based ranking), and `DraftsCache` (with save/delete/get operations). |
| `folder_cache.go` | Caches IMAP folder listings per account and per-folder email metadata. Stores folder names to avoid repeated IMAP `LIST` commands, and caches email headers per folder for fast navigation. |
| `signature.go` | Loads and saves the user's email signature from `~/.config/matcha/signature.txt`. |
| `oauth.go` | OAuth2 integration — token retrieval, authorization flow launcher, and embedded Python helper extraction. |
| `oauth_script.py` | Embedded Gmail OAuth2 helper script (browser-based auth, token refresh, secure storage). |
| `config_test.go` | Unit tests for configuration logic. |

## OAuth2 / XOAUTH2

Accounts with `auth_method: "oauth2"` use Gmail's XOAUTH2 mechanism instead of passwords. The flow works across three layers:

1. **`config/oauth.go`** — Go-side orchestration. Extracts the embedded Python helper to `~/.config/matcha/oauth/`, invokes it to run the browser-based authorization flow (`RunOAuth2Flow`) or to retrieve a fresh access token (`GetOAuth2Token`). The `IsOAuth2()` method on `Account` checks the auth method.

2. **`config/oauth_script.py`** — Embedded Python script that handles the full OAuth2 lifecycle:
- `auth` — Opens a browser for Google authorization, captures the callback on `localhost:8189`, exchanges the code for tokens, and saves them to `~/.config/matcha/oauth_tokens/`.
- `token` — Returns a fresh access token, automatically refreshing if expired (with a 5-minute buffer).
- `revoke` — Revokes tokens with Google and deletes local storage.
- Client credentials are stored in `~/.config/matcha/oauth_client.json`.

3. **`fetcher/xoauth2.go`** — Implements the XOAUTH2 SASL mechanism (`sasl.Client` interface) for IMAP/SMTP authentication. Formats the initial response as `user=<email>\x01auth=Bearer <token>\x01\x01` per Google's XOAUTH2 protocol spec.
5 changes: 5 additions & 0 deletions fetcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ This package is the IMAP client layer for Matcha. It:
- Supports S/MIME decryption (opaque and enveloped) and detached signature verification
- Provides mailbox operations: delete (expunge), archive (move), and folder-to-folder moves
- Exposes both mailbox-specific and convenience functions (e.g., `FetchEmails` defaults to INBOX)
- Supports XOAUTH2 SASL authentication for Gmail OAuth2 accounts (see `xoauth2.go`)

## XOAUTH2

The `xoauth2.go` file implements the XOAUTH2 SASL mechanism as a `sasl.Client`. When an account uses `auth_method: "oauth2"`, the fetcher calls `config.GetOAuth2Token()` to get a fresh access token, then authenticates the IMAP connection using this SASL client instead of a password. The initial response follows Google's XOAUTH2 protocol: `user=<email>\x01auth=Bearer <token>\x01\x01`.
55 changes: 55 additions & 0 deletions plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# plugin

Lua-based plugin system for extending Matcha. Plugins are loaded from `~/.config/matcha/plugins/` and run inside a sandboxed Lua VM (no `os`, `io`, or `debug` libraries).

## How it works

The `Manager` creates a Lua VM at startup, registers the `matcha` module, and loads all plugins from the user's plugins directory. Plugins can be either a single `.lua` file or a directory with an `init.lua` entry point.

Plugins interact with Matcha by registering callbacks on hooks:

```lua
local matcha = require("matcha")

matcha.on("email_received", function(email)
matcha.log("New email from: " .. email.from)
matcha.notify("New mail!", 3)
end)
```

## Lua API (`matcha` module)

| Function | Description |
|----------|-------------|
| `matcha.on(event, callback)` | Register a callback for a hook event |
| `matcha.log(msg)` | Log a message to stderr |
| `matcha.notify(msg [, seconds])` | Show a temporary notification in the TUI (default 2s) |
| `matcha.set_status(area, text)` | Set a persistent status string for a view area (`"inbox"`, `"composer"`, `"email_view"`) |

## Hook events

| Event | Callback argument | Description |
|-------|-------------------|-------------|
| `startup` | — | Matcha has started |
| `shutdown` | — | Matcha is exiting |
| `email_received` | Lua table with `uid`, `from`, `to`, `subject`, `date`, `is_read`, `account_id`, `folder` | New email arrived |
| `email_viewed` | Same as `email_received` | User opened an email |
| `email_send_before` | Table with `to`, `cc`, `subject`, `account_id` | About to send an email |
| `email_send_after` | Same as `email_send_before` | Email sent successfully |
| `folder_changed` | Folder name (string) | User switched folders |
| `composer_updated` | Table with `body`, `body_len`, `subject`, `to` | Composer content changed |

## Available plugins

The following example plugins ship in `~/.config/matcha/plugins/`:

- `email_age.lua`
- `recipient_counter.lua`

## Files

| File | Description |
|------|-------------|
| `plugin.go` | Plugin manager — Lua VM setup, plugin discovery and loading, notification/status state |
| `hooks.go` | Hook definitions, callback registration, and hook invocation helpers |
| `api.go` | `matcha` Lua module registration (`on`, `log`, `notify`, `set_status`) |
Loading