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

5.2 KiB

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:

make build
# Or cross-compile for all platforms:
make build-all

Basic usage (incremental mode - only downloads new emails):

./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):

./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -full

Testing/debugging with limited emails:

./imapdown -server imap.example.com -email user@example.com -user user@example.com -password "password" -ssl -limit 10

Custom storage directory:

./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:

make build

Cross-compile for all platforms:

make build-all
# Produces: imapdown-linux-amd64, imapdown-linux-arm64,
#           imapdown-darwin-amd64, imapdown-darwin-arm64,
#           imapdown-windows-amd64.exe

Install to $GOPATH/bin:

make install

Clean build artifacts:

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