MCPcopy Index your code
hub / github.com/BrianLeishman/go-imap

github.com/BrianLeishman/go-imap @v0.1.28

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.1.28 ↗ · + Follow
390 symbols 1,327 edges 40 files 177 documented · 45%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Go IMAP Client (go-imap)

Go Reference CI codecov Go Report Card

Simple, pragmatic IMAP client for Go (Golang) with TLS, LOGIN or XOAUTH2 (OAuth 2.0), IDLE notifications, robust reconnects, and batteries‑included helpers for searching, fetching, moving, and flagging messages.

Works great with Gmail, Office 365/Exchange, and most RFC‑compliant IMAP servers.

Features

  • TLS connections and timeouts (DialTimeout, CommandTimeout)
  • Authentication via LOGIN and XOAUTH2
  • Folders: list, select/examine, create, delete, rename, error-tolerant counting
  • Search: UID SEARCH helpers, type-safe SearchBuilder with fluent API, RFC 3501 literal syntax for non-ASCII text
  • Fetch: envelope, flags, size, text/HTML bodies, attachments
  • Mutations: move, copy, append (upload), set flags, delete + expunge
  • IMAP IDLE with event handlers for EXISTS, EXPUNGE, FETCH
  • Automatic reconnect with re-auth and folder restore
  • Robust folder handling with graceful error recovery for problematic folders

Install

go get github.com/BrianLeishman/go-imap

Requires Go 1.25+ (see go.mod).

Quick Start

Basic Connection (LOGIN)

package main

import (
    "fmt"
    "time"
    imap "github.com/BrianLeishman/go-imap"
)

func main() {
    // Optional configuration
    imap.Verbose = false      // Enable to emit debug-level IMAP logs
    imap.RetryCount = 3        // Number of retries for failed commands
    imap.DialTimeout = 10 * time.Second
    imap.CommandTimeout = 30 * time.Second

    // For self-signed certificates (use with caution!)
    // imap.TLSSkipVerify = true

    // Connect with standard LOGIN authentication
    m, err := imap.New("username", "password", "mail.server.com", 993)
    if err != nil { panic(err) }
    defer m.Close()

    // Quick test
    folders, err := m.GetFolders()
    if err != nil { panic(err) }
    fmt.Printf("Connected! Found %d folders\n", len(folders))
}

OAuth 2.0 Authentication (XOAUTH2)

// Connect with OAuth2 (Gmail, Office 365, etc.)
m, err := imap.NewWithOAuth2("user@example.com", accessToken, "imap.gmail.com", 993)
if err != nil { panic(err) }
defer m.Close()

// The OAuth2 connection works exactly like LOGIN after authentication
if err := m.SelectFolder("INBOX"); err != nil { panic(err) }

Logging

The client uses Go's log/slog package for structured logging. By default it emits info, warning, and error events to standard error with the component attribute set to imap/agent. Opt-in debug output is controlled by the existing imap.Verbose flag:

imap.Verbose = true // Log every IMAP command/response at debug level

You can plug in your own logger implementation via imap.SetLogger. For *slog.Logger specifically, call imap.SetSlogLogger. When unset, the library falls back to a text handler.

import (
    "log/slog"
    "os"
    imap "github.com/BrianLeishman/go-imap"
)

handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
imap.SetSlogLogger(slog.New(handler))

Call imap.SetLogger(nil) to reset to the built-in logger. When verbose mode is enabled you can further reduce noise by setting imap.SkipResponses = true to suppress raw server responses.

Examples

Complete, runnable example programs are available in the examples/ directory. Each example demonstrates specific features and can be run directly:

go run examples/basic_connection/main.go

Available Examples

Getting Started

Working with Emails

  • folders - List, create, rename, delete folders; select/examine; get email counts
  • search - Search emails with raw criteria and the type-safe SearchBuilder
  • literal_search - Search with non-ASCII characters using RFC 3501 literal syntax
  • fetch_emails - Fetch email headers (fast) and full content with attachments (slower)
  • email_operations - Move, copy, append emails; set/remove flags; delete and expunge

Advanced Features

Detailed Usage Examples

1. Working with Folders

// List all folders
folders, err := m.GetFolders()
if err != nil { panic(err) }

// Example output:
// folders = []string{
//     "INBOX",
//     "Sent",
//     "Drafts",
//     "Trash",
//     "INBOX/Receipts",
//     "INBOX/Important",
//     "[Gmail]/All Mail",
//     "[Gmail]/Spam",
// }

for _, folder := range folders {
    fmt.Println("Folder:", folder)
}

// Select a folder for operations (read-write mode)
err = m.SelectFolder("INBOX")
if err != nil { panic(err) }

// Select folder in read-only mode
err = m.ExamineFolder("INBOX")
if err != nil { panic(err) }

// Get total email count across all folders
totalCount, err := m.GetTotalEmailCount()
if err != nil { panic(err) }
fmt.Printf("Total emails in all folders: %d\n", totalCount)

// Get count excluding certain folders
excludedFolders := []string{"Trash", "[Gmail]/Spam"}
count, err := m.GetTotalEmailCountExcluding(excludedFolders)
if err != nil { panic(err) }
fmt.Printf("Total emails (excluding spam/trash): %d\n", count)

// Error-tolerant counting (continues even if some folders fail)
// This is especially useful with Gmail or other providers that have inaccessible system folders
safeCount, folderErrors, err := m.GetTotalEmailCountSafe()
if err != nil { panic(err) }
fmt.Printf("Total accessible emails: %d\n", safeCount)

if len(folderErrors) > 0 {
    fmt.Printf("Note: %d folders had errors:\n", len(folderErrors))
    for _, folderErr := range folderErrors {
        fmt.Printf("  - %v\n", folderErr)
    }
}
// Example output:
// Total accessible emails: 1247
// Note: 2 folders had errors:
//   - folder "[Gmail]": NO [NONEXISTENT] Unknown Mailbox
//   - folder "[Gmail]/All Mail": NO [NONEXISTENT] Unknown Mailbox

// Create, rename, and delete folders
err = m.CreateFolder("INBOX/Projects")
if err != nil { panic(err) }

err = m.RenameFolder("INBOX/Projects", "INBOX/Archive")
if err != nil { panic(err) }

err = m.DeleteFolder("INBOX/Archive")
if err != nil { panic(err) }

// Get detailed statistics for each folder (includes max UID)
stats, err := m.GetFolderStats()
if err != nil { panic(err) }

fmt.Printf("Found %d folders:\n", len(stats))
for _, stat := range stats {
    if stat.Error != nil {
        fmt.Printf("  %-20s [ERROR]: %v\n", stat.Name, stat.Error)
    } else {
        fmt.Printf("  %-20s %5d emails, max UID: %d\n",
            stat.Name, stat.Count, stat.MaxUID)
    }
}
// Example output:
// Found 8 folders:
//   INBOX                 342 emails, max UID: 1543
//   Sent                   89 emails, max UID: 234
//   Drafts                  3 emails, max UID: 67
//   Trash                  12 emails, max UID: 89
//   [Gmail]          [ERROR]: NO [NONEXISTENT] Unknown Mailbox
//   [Gmail]/Spam           0 emails, max UID: 0
//   INBOX/Archive        801 emails, max UID: 2156
//   INBOX/Important       45 emails, max UID: 987

1.1. Handling Problematic Folders

Some IMAP servers (especially Gmail) have special system folders that cannot be examined or may return errors. The traditional GetTotalEmailCount() method will fail completely if any folder is inaccessible, but the new safe methods continue processing other folders.

When to Use Safe Methods

  • Gmail users: Gmail's [Gmail] folder often returns "NO [NONEXISTENT] Unknown Mailbox"
  • Exchange/Office 365: Some system folders may be restricted
  • Custom IMAP servers: Servers with permission-restricted folders
  • Production applications: When you need reliable email counting despite folder issues
// Traditional approach - fails if ANY folder has issues
totalCount, err := m.GetTotalEmailCount()
if err != nil {
    // This will fail completely if "[Gmail]" folder is inaccessible
    fmt.Printf("Count failed: %v\n", err)
    // Output: Count failed: EXAMINE command failed: NO [NONEXISTENT] Unknown Mailbox
}

// Safe approach - continues despite folder errors
safeCount, folderErrors, err := m.GetTotalEmailCountSafe()
if err != nil {
    // Only fails on serious connection issues, not individual folder problems
    panic(err)
}

fmt.Printf("Counted %d emails from accessible folders\n", safeCount)
if len(folderErrors) > 0 {
    fmt.Printf("Skipped %d problematic folders\n", len(folderErrors))
}

// Safe exclusion - combine error tolerance with folder filtering
excludedFolders := []string{"Trash", "Junk", "Deleted Items"}
count, folderErrors, err := m.GetTotalEmailCountSafeExcluding(excludedFolders)
if err != nil { panic(err) }

fmt.Printf("Active emails: %d (excluding trash/spam and skipping errors)\n", count)

// Detailed analysis with error handling
stats, err := m.GetFolderStats()
if err != nil { panic(err) }

accessibleFolders := 0
totalEmails := 0
maxUID := 0

for _, stat := range stats {
    if stat.Error != nil {
        fmt.Printf("⚠️  %s: %v\n", stat.Name, stat.Error)
        continue
    }

    accessibleFolders++
    totalEmails += stat.Count
    if stat.MaxUID > maxUID {
        maxUID = stat.MaxUID
    }

    fmt.Printf("✅ %-25s %5d emails (UID range: 1-%d)\n",
        stat.Name, stat.Count, stat.MaxUID)
}

fmt.Printf("\nSummary: %d/%d folders accessible, %d total emails, highest UID: %d\n",
    accessibleFolders, len(stats), totalEmails, maxUID)

Error Types You Might Encounter

stats, err := m.GetFolderStats()
if err != nil { panic(err) }

for _, stat := range stats {
    if stat.Error != nil {
        fmt.Printf("Folder '%s' error: %v\n", stat.Name, stat.Error)

        // Common error patterns:
        if strings.Contains(stat.Error.Error(), "NONEXISTENT") {
            fmt.Printf("  → This is a virtual/system folder that can't be examined\n")
        } else if strings.Contains(stat.Error.Error(), "permission") {
            fmt.Printf("  → This folder requires special permissions\n")
        } else {
            fmt.Printf("  → Unexpected error, might indicate connection issues\n")
        }
    }
}

2. Searching for Emails

```go // Select folder first err := m.SelectFolder("INBOX") if err != nil { panic(err) }

// Basic searches - returns slice of UIDs allUIDs, _ := m.GetUIDs("ALL") // All emails unseenUIDs, _ := m.GetUIDs("UNSEEN") // Unread emails recentUIDs, _ := m.GetUIDs("RECENT") // Recent emails seenUIDs, _ := m.GetUIDs("SEEN") // Read emails flaggedUIDs, _ := m.GetUIDs("FLAGGED") // Starred/flagged emails

// Example output: fmt.Printf("Found %d total emails\n", len(allUIDs)) // Found 342 total emails fmt.Printf("Found %d unread emails\n", len(unseenUIDs)) // Found 12 unread emails fmt.Printf("UIDs of unread: %v\n", unseenUIDs) // UIDs of unread: [245 246 247 251 252 253 254 255 256 257 258 259]

// Date-based searches todayUIDs, _ := m.GetUIDs("ON 15-Sep-2024") sinceUIDs, _ := m.GetUIDs("SINCE 10-Sep-2024") beforeUIDs, _ := m.GetUIDs("BEFORE 20-Sep-2024") rangeUIDs, _ := m.GetUIDs("SINCE 1-Sep-2024 BEFORE 30-Sep-2024")

// From/To searches fromBossUIDs, _ := m.GetUIDs(FROM "boss@company.com") toMeUIDs, _ := m.GetUIDs(TO "me@company.com")

// Subject/body searches subjectUIDs, _ := m.GetUIDs(SUBJECT "invoice") bodyUIDs, _ := m.GetUIDs(BODY "payment") textUIDs, _ := m.GetUIDs(TEXT "urgent") // Searches both subject and body

// Complex searches complexUIDs, _ := m.GetUIDs(UNSEEN FROM "support@github.com" SINCE 1-Sep-2024)

// UID ranges (raw IMAP syntax) firstUID, _ := m.GetUIDs("1") // UID 1 only lastUID, _ := m.GetUIDs("*") // Highest UID only rangeUIDs, _ := m.GetUIDs("1:10") // UIDs 1 through 10

// Get the N most recent messages (recommended for "last N" queries) last10UIDs, _ := m.GetLastNUIDs(10) // Last 10 messages by UID

// Cheaper method to retrieve the latest UID (requires RFC-4731; // not all servers support this — check the error). maxUID, _ := m.GetMaxUID() // Highest UID only

// Size-based searches largeUIDs, _ := m.GetUIDs("LARGER 10485760") // Emails larger than 10MB smallUIDs, _ := m.GetUIDs("SMALLER 1024") // Emails smaller than 1KB

// Non-ASCII searches using RFC 3501 literal syntax // The library automatically detects and handles literal syntax {n} // where n is the byte count of the following data

// Search for Cyrillic text in subject (тест = 8 bytes in UTF-8) cyrillicUIDs, _ := m.GetUIDs("CHARSET UTF-8 Subject {8}\r\nтест")

// Search for Chinese text in subject (测试 = 6 bytes in UTF-8)
chineseUIDs, _ := m.GetUIDs("CHARSET UTF-8 Subject {6}\r\n测试")

// Search for Japanese text in body (テスト = 9 bytes in UTF-8) japaneseUIDs, _ := m.GetUIDs("CHARSET UTF-8 BODY {9}\r\nテスト")

// Search for Arabic text (اختبار = 12 bytes in UTF-8) arabicUIDs, _ := m.GetUIDs("CHARSET UTF-8 TEXT {12}\r\nاختبار")

// Search

Extension points exported contracts — how you extend this code

Logger (Interface)
Logger defines the minimal logging interface used by the IMAP client. Implementations must be safe for concurrent use. [1 …
logger.go

Core symbols most depended-on inside this repo

GetUIDs
called by 56
message.go
Error
called by 46
logger.go
Search
called by 39
search.go
Close
called by 38
conn.go
SelectFolder
called by 27
folder.go
CheckType
called by 27
parse.go
New
called by 22
search.go
parseFetchTokens
called by 21
parse.go

Shape

Function 252
Method 118
Struct 15
TypeAlias 4
Interface 1

Languages

Go100%

Modules by API surface

message_string_test.go47 symbols
search.go42 symbols
parse_coverage_test.go32 symbols
parse.go24 symbols
message.go24 symbols
logger.go22 symbols
folder.go20 symbols
auth_test.go14 symbols
idle_test.go13 symbols
examples/email_operations/main.go12 symbols
logger_test.go10 symbols
idle.go10 symbols

For agents

$ claude mcp add go-imap \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page