Files
steve eb63d8cbc1 Rewrite from Python to Go for single-binary cross-platform builds
Replaces imapdown.py with a multi-file Go implementation using
github.com/emersion/go-imap/v2. All features preserved: SSL/STARTTLS,
incremental UID-based downloads, attachment extraction to zip,
modified UTF-7 folder name decoding, and full-mode safety checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 17:54:41 +00:00

152 lines
5.2 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This project downloads all emails from an IMAP server into individual EML files, preserving the folder hierarchy.
Built with Go as a single self-contained binary, fast and cross-platform.
## Development Environment
- Go 1.21+ required
- Dependencies: `github.com/emersion/go-imap/v2` (auto-installed via `go mod tidy`)
- Build with: `make build` or `go build`
- Cross-compile with: `make build-all`
## Running the Application
First, build the binary:
```bash
make build
# Or cross-compile for all platforms:
make build-all
```
Basic usage (incremental mode - only downloads new emails):
```bash
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl
```
Full download (ignores previous state, requires empty target directory):
```bash
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -full
```
Testing/debugging with limited emails:
```bash
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -limit 10
```
Custom storage directory:
```bash
./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -output /path/to/backup
```
## Architecture
### Implementation Structure
The code is organized into multiple files for clarity:
- `main.go` - Entry point, CLI parsing, orchestration
- `imap.go` - IMAP connection and folder operations
- `email.go` - Email parsing and attachment extraction
- `state.go` - State file management (JSON)
- `filename.go` - Filename sanitization and Modified UTF-7 decoding
- `Makefile` - Build targets
### State Tracking
- The script maintains a `.imapdown_state.json` file in each email account's download folder
- Tracks the highest UID (unique identifier) downloaded per IMAP folder
- Format: `{"INBOX": 19334, "INBOX.Archive": 1770, "Sent": 892}`
- Enables efficient incremental downloads (default mode)
### Download Flow
1. Parse arguments
2. Connect to IMAP server (SSL, STARTTLS, or plain)
3. List all folders and decode modified UTF-7 folder names
4. For each folder:
- Load last downloaded UID from state file (if incremental mode)
- Search for new messages (UID > last_uid)
- Download each message as RFC822
- Save as `.eml` file with naming: `{UID}_{date}_{subject}.eml`
- Extract attachments into `.zip` file (same base name)
- Update state with highest UID
5. Save state file
### Key Implementation Details
**Modified UTF-7 Decoding**: IMAP folder names use modified UTF-7 encoding. This is not standard base64 - it uses `,` instead of `/` and has special `&` handling. Implemented in `DecodeModifiedUTF7()` in `filename.go`.
**Filename Sanitization**: Two-stage process:
- `SanitizeFilename()`: Removes invalid filesystem characters, max 50 chars for subjects
- `SanitizeFolderPath()`: Converts IMAP folder separators (`.` or `/`) to OS path separators
**UID-Based Incremental Updates**: Uses IMAP UIDs (not sequence numbers) because UIDs are persistent. When `lastUID > 0`, searches for UIDs > lastUID. On first run (`lastUID == 0`), searches for all messages using an empty SearchCriteria. Some servers return the highest UID even when searching for higher UIDs, so there's additional filtering.
**Full Mode Safety**: `-full` mode checks if the download folder already contains `.eml` files and refuses to run. This prevents accidental duplicates. Users must delete the folder first.
**Attachment Handling**:
- Walks message parts looking for `Content-Disposition: attachment` or `inline`
- Handles duplicate attachment filenames by appending `_{counter}`
- All attachments for one email go into a single `.zip` file
## Output Structure
Without `-output` flag (default: `./{email_address}`):
```
{email_address}/ # sanitized email address in current directory
├── .imapdown_state.json
├── INBOX/
│ ├── 123_20240115_Meeting_notes.eml
│ └── 124_20240116_Report.zip
└── Sent/
└── 456_20240114_RE_Question.eml
```
With `-output /path/to/backup`:
```
/path/to/backup/ # specified output directory used directly
├── .imapdown_state.json
├── INBOX/
│ ├── 123_20240115_Meeting_notes.eml
│ └── 124_20240116_Report.zip
└── Sent/
└── 456_20240114_RE_Question.eml
```
## Building and Installing
Build for current platform:
```bash
make build
```
Cross-compile for all platforms:
```bash
make build-all
# Produces: imapdown-linux-amd64, imapdown-linux-arm64,
# imapdown-darwin-amd64, imapdown-darwin-arm64,
# imapdown-windows-amd64.exe
```
Install to `$GOPATH/bin`:
```bash
make install
```
Clean build artifacts:
```bash
make clean
```
## Testing
No formal test suite exists. Manual testing approach:
- Use `-limit 10` to download a small batch for verification
- Test SSL vs STARTTLS connections
- Test incremental mode by running twice
- Verify `.eml` files open correctly in email clients
- Check that folders with special characters (non-ASCII) are handled correctly
- Test first run (no state file) to ensure all messages are downloaded