{"id":47410304,"url":"https://github.com/BrianLeishman/go-imap","last_synced_at":"2026-03-28T14:00:50.488Z","repository":{"id":34317638,"uuid":"147899998","full_name":"BrianLeishman/go-imap","owner":"BrianLeishman","description":"Super Simple IMAP Client Library for Golang","archived":false,"fork":false,"pushed_at":"2026-03-18T17:34:57.000Z","size":2900,"stargazers_count":96,"open_issues_count":0,"forks_count":35,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-03-19T03:52:40.520Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BrianLeishman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2018-09-08T04:10:46.000Z","updated_at":"2026-03-18T17:34:45.000Z","dependencies_parsed_at":"2023-09-23T16:49:24.419Z","dependency_job_id":"d77e20b1-814f-4c5f-86fb-5358066f8f84","html_url":"https://github.com/BrianLeishman/go-imap","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/BrianLeishman/go-imap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrianLeishman%2Fgo-imap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrianLeishman%2Fgo-imap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrianLeishman%2Fgo-imap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrianLeishman%2Fgo-imap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BrianLeishman","download_url":"https://codeload.github.com/BrianLeishman/go-imap/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BrianLeishman%2Fgo-imap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31102536,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-28T13:41:34.766Z","status":"ssl_error","status_checked_at":"2026-03-28T13:41:05.465Z","response_time":79,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-20T23:00:38.117Z","updated_at":"2026-03-28T14:00:50.481Z","avatar_url":"https://github.com/BrianLeishman.png","language":"Go","funding_links":[],"categories":["Email"],"sub_categories":["Search and Analytic Databases"],"readme":"# Go IMAP Client (go-imap)\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/BrianLeishman/go-imap.svg)](https://pkg.go.dev/github.com/BrianLeishman/go-imap)\n[![CI](https://github.com/BrianLeishman/go-imap/actions/workflows/go.yml/badge.svg)](https://github.com/BrianLeishman/go-imap/actions/workflows/go.yml)\n[![codecov](https://codecov.io/gh/BrianLeishman/go-imap/branch/master/graph/badge.svg)](https://codecov.io/gh/BrianLeishman/go-imap)\n[![Go Report Card](https://goreportcard.com/badge/github.com/BrianLeishman/go-imap)](https://goreportcard.com/report/github.com/BrianLeishman/go-imap)\n\nSimple, 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.\n\nWorks great with Gmail, Office 365/Exchange, and most RFC‑compliant IMAP servers.\n\n## Features\n\n- TLS connections and timeouts (`DialTimeout`, `CommandTimeout`)\n- Authentication via `LOGIN` and `XOAUTH2`\n- Folders: list, select/examine, create, delete, rename, error-tolerant counting\n- Search: `UID SEARCH` helpers, type-safe `SearchBuilder` with fluent API, RFC 3501 literal syntax for non-ASCII text\n- Fetch: envelope, flags, size, text/HTML bodies, attachments\n- Mutations: move, copy, append (upload), set flags, delete + expunge\n- IMAP IDLE with event handlers for `EXISTS`, `EXPUNGE`, `FETCH`\n- Automatic reconnect with re-auth and folder restore\n- Robust folder handling with graceful error recovery for problematic folders\n\n## Install\n\n```bash\ngo get github.com/BrianLeishman/go-imap\n```\n\nRequires Go 1.25+ (see `go.mod`).\n\n## Quick Start\n\n### Basic Connection (LOGIN)\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"time\"\n    imap \"github.com/BrianLeishman/go-imap\"\n)\n\nfunc main() {\n    // Optional configuration\n    imap.Verbose = false      // Enable to emit debug-level IMAP logs\n    imap.RetryCount = 3        // Number of retries for failed commands\n    imap.DialTimeout = 10 * time.Second\n    imap.CommandTimeout = 30 * time.Second\n\n    // For self-signed certificates (use with caution!)\n    // imap.TLSSkipVerify = true\n\n    // Connect with standard LOGIN authentication\n    m, err := imap.New(\"username\", \"password\", \"mail.server.com\", 993)\n    if err != nil { panic(err) }\n    defer m.Close()\n\n    // Quick test\n    folders, err := m.GetFolders()\n    if err != nil { panic(err) }\n    fmt.Printf(\"Connected! Found %d folders\\n\", len(folders))\n}\n```\n\n### OAuth 2.0 Authentication (XOAUTH2)\n\n```go\n// Connect with OAuth2 (Gmail, Office 365, etc.)\nm, err := imap.NewWithOAuth2(\"user@example.com\", accessToken, \"imap.gmail.com\", 993)\nif err != nil { panic(err) }\ndefer m.Close()\n\n// The OAuth2 connection works exactly like LOGIN after authentication\nif err := m.SelectFolder(\"INBOX\"); err != nil { panic(err) }\n```\n\n## Logging\n\nThe client uses Go's `log/slog` package for structured logging. By default it\nemits info, warning, and error events to standard error with the `component`\nattribute set to `imap/agent`. Opt-in debug output is controlled by the\nexisting `imap.Verbose` flag:\n\n```go\nimap.Verbose = true // Log every IMAP command/response at debug level\n```\n\nYou can plug in your own logger implementation via `imap.SetLogger`. For\n`*slog.Logger` specifically, call `imap.SetSlogLogger`. When unset, the library\nfalls back to a text handler.\n\n```go\nimport (\n    \"log/slog\"\n    \"os\"\n    imap \"github.com/BrianLeishman/go-imap\"\n)\n\nhandler := slog.NewJSONHandler(os.Stdout, \u0026slog.HandlerOptions{Level: slog.LevelDebug})\nimap.SetSlogLogger(slog.New(handler))\n```\n\nCall `imap.SetLogger(nil)` to reset to the built-in logger. When verbose mode is\nenabled you can further reduce noise by setting `imap.SkipResponses = true` to\nsuppress raw server responses.\n\n## Examples\n\nComplete, runnable example programs are available in the [`examples/`](examples/) directory. Each example demonstrates specific features and can be run directly:\n\n```bash\ngo run examples/basic_connection/main.go\n```\n\n### Available Examples\n\n#### Getting Started\n\n- [`basic_connection`](examples/basic_connection/main.go) - Basic LOGIN authentication and connection setup\n- [`oauth2_connection`](examples/oauth2_connection/main.go) - OAuth 2.0 (XOAUTH2) authentication for Gmail/Office 365\n\n#### Working with Emails\n\n- [`folders`](examples/folders/main.go) - List, create, rename, delete folders; select/examine; get email counts\n- [`search`](examples/search/main.go) - Search emails with raw criteria and the type-safe SearchBuilder\n- [`literal_search`](examples/literal_search/main.go) - Search with non-ASCII characters using RFC 3501 literal syntax\n- [`fetch_emails`](examples/fetch_emails/main.go) - Fetch email headers (fast) and full content with attachments (slower)\n- [`email_operations`](examples/email_operations/main.go) - Move, copy, append emails; set/remove flags; delete and expunge\n\n#### Advanced Features\n\n- [`idle_monitoring`](examples/idle_monitoring/main.go) - Real-time email notifications with IDLE\n- [`error_handling`](examples/error_handling/main.go) - Robust error handling, reconnection, and timeout configuration\n- [`complete_example`](examples/complete_example/main.go) - Full-featured example combining multiple operations\n\n## Detailed Usage Examples\n\n### 1. Working with Folders\n\n```go\n// List all folders\nfolders, err := m.GetFolders()\nif err != nil { panic(err) }\n\n// Example output:\n// folders = []string{\n//     \"INBOX\",\n//     \"Sent\",\n//     \"Drafts\",\n//     \"Trash\",\n//     \"INBOX/Receipts\",\n//     \"INBOX/Important\",\n//     \"[Gmail]/All Mail\",\n//     \"[Gmail]/Spam\",\n// }\n\nfor _, folder := range folders {\n    fmt.Println(\"Folder:\", folder)\n}\n\n// Select a folder for operations (read-write mode)\nerr = m.SelectFolder(\"INBOX\")\nif err != nil { panic(err) }\n\n// Select folder in read-only mode\nerr = m.ExamineFolder(\"INBOX\")\nif err != nil { panic(err) }\n\n// Get total email count across all folders\ntotalCount, err := m.GetTotalEmailCount()\nif err != nil { panic(err) }\nfmt.Printf(\"Total emails in all folders: %d\\n\", totalCount)\n\n// Get count excluding certain folders\nexcludedFolders := []string{\"Trash\", \"[Gmail]/Spam\"}\ncount, err := m.GetTotalEmailCountExcluding(excludedFolders)\nif err != nil { panic(err) }\nfmt.Printf(\"Total emails (excluding spam/trash): %d\\n\", count)\n\n// Error-tolerant counting (continues even if some folders fail)\n// This is especially useful with Gmail or other providers that have inaccessible system folders\nsafeCount, folderErrors, err := m.GetTotalEmailCountSafe()\nif err != nil { panic(err) }\nfmt.Printf(\"Total accessible emails: %d\\n\", safeCount)\n\nif len(folderErrors) \u003e 0 {\n    fmt.Printf(\"Note: %d folders had errors:\\n\", len(folderErrors))\n    for _, folderErr := range folderErrors {\n        fmt.Printf(\"  - %v\\n\", folderErr)\n    }\n}\n// Example output:\n// Total accessible emails: 1247\n// Note: 2 folders had errors:\n//   - folder \"[Gmail]\": NO [NONEXISTENT] Unknown Mailbox\n//   - folder \"[Gmail]/All Mail\": NO [NONEXISTENT] Unknown Mailbox\n\n// Create, rename, and delete folders\nerr = m.CreateFolder(\"INBOX/Projects\")\nif err != nil { panic(err) }\n\nerr = m.RenameFolder(\"INBOX/Projects\", \"INBOX/Archive\")\nif err != nil { panic(err) }\n\nerr = m.DeleteFolder(\"INBOX/Archive\")\nif err != nil { panic(err) }\n\n// Get detailed statistics for each folder (includes max UID)\nstats, err := m.GetFolderStats()\nif err != nil { panic(err) }\n\nfmt.Printf(\"Found %d folders:\\n\", len(stats))\nfor _, stat := range stats {\n    if stat.Error != nil {\n        fmt.Printf(\"  %-20s [ERROR]: %v\\n\", stat.Name, stat.Error)\n    } else {\n        fmt.Printf(\"  %-20s %5d emails, max UID: %d\\n\",\n            stat.Name, stat.Count, stat.MaxUID)\n    }\n}\n// Example output:\n// Found 8 folders:\n//   INBOX                 342 emails, max UID: 1543\n//   Sent                   89 emails, max UID: 234\n//   Drafts                  3 emails, max UID: 67\n//   Trash                  12 emails, max UID: 89\n//   [Gmail]          [ERROR]: NO [NONEXISTENT] Unknown Mailbox\n//   [Gmail]/Spam           0 emails, max UID: 0\n//   INBOX/Archive        801 emails, max UID: 2156\n//   INBOX/Important       45 emails, max UID: 987\n```\n\n### 1.1. Handling Problematic Folders\n\nSome 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.\n\n#### When to Use Safe Methods\n\n- **Gmail users**: Gmail's `[Gmail]` folder often returns \"NO [NONEXISTENT] Unknown Mailbox\"\n- **Exchange/Office 365**: Some system folders may be restricted\n- **Custom IMAP servers**: Servers with permission-restricted folders\n- **Production applications**: When you need reliable email counting despite folder issues\n\n```go\n// Traditional approach - fails if ANY folder has issues\ntotalCount, err := m.GetTotalEmailCount()\nif err != nil {\n    // This will fail completely if \"[Gmail]\" folder is inaccessible\n    fmt.Printf(\"Count failed: %v\\n\", err)\n    // Output: Count failed: EXAMINE command failed: NO [NONEXISTENT] Unknown Mailbox\n}\n\n// Safe approach - continues despite folder errors\nsafeCount, folderErrors, err := m.GetTotalEmailCountSafe()\nif err != nil {\n    // Only fails on serious connection issues, not individual folder problems\n    panic(err)\n}\n\nfmt.Printf(\"Counted %d emails from accessible folders\\n\", safeCount)\nif len(folderErrors) \u003e 0 {\n    fmt.Printf(\"Skipped %d problematic folders\\n\", len(folderErrors))\n}\n\n// Safe exclusion - combine error tolerance with folder filtering\nexcludedFolders := []string{\"Trash\", \"Junk\", \"Deleted Items\"}\ncount, folderErrors, err := m.GetTotalEmailCountSafeExcluding(excludedFolders)\nif err != nil { panic(err) }\n\nfmt.Printf(\"Active emails: %d (excluding trash/spam and skipping errors)\\n\", count)\n\n// Detailed analysis with error handling\nstats, err := m.GetFolderStats()\nif err != nil { panic(err) }\n\naccessibleFolders := 0\ntotalEmails := 0\nmaxUID := 0\n\nfor _, stat := range stats {\n    if stat.Error != nil {\n        fmt.Printf(\"⚠️  %s: %v\\n\", stat.Name, stat.Error)\n        continue\n    }\n\n    accessibleFolders++\n    totalEmails += stat.Count\n    if stat.MaxUID \u003e maxUID {\n        maxUID = stat.MaxUID\n    }\n\n    fmt.Printf(\"✅ %-25s %5d emails (UID range: 1-%d)\\n\",\n        stat.Name, stat.Count, stat.MaxUID)\n}\n\nfmt.Printf(\"\\nSummary: %d/%d folders accessible, %d total emails, highest UID: %d\\n\",\n    accessibleFolders, len(stats), totalEmails, maxUID)\n```\n\n#### Error Types You Might Encounter\n\n```go\nstats, err := m.GetFolderStats()\nif err != nil { panic(err) }\n\nfor _, stat := range stats {\n    if stat.Error != nil {\n        fmt.Printf(\"Folder '%s' error: %v\\n\", stat.Name, stat.Error)\n\n        // Common error patterns:\n        if strings.Contains(stat.Error.Error(), \"NONEXISTENT\") {\n            fmt.Printf(\"  → This is a virtual/system folder that can't be examined\\n\")\n        } else if strings.Contains(stat.Error.Error(), \"permission\") {\n            fmt.Printf(\"  → This folder requires special permissions\\n\")\n        } else {\n            fmt.Printf(\"  → Unexpected error, might indicate connection issues\\n\")\n        }\n    }\n}\n```\n\n### 2. Searching for Emails\n\n```go\n// Select folder first\nerr := m.SelectFolder(\"INBOX\")\nif err != nil { panic(err) }\n\n// Basic searches - returns slice of UIDs\nallUIDs, _ := m.GetUIDs(\"ALL\")           // All emails\nunseenUIDs, _ := m.GetUIDs(\"UNSEEN\")     // Unread emails\nrecentUIDs, _ := m.GetUIDs(\"RECENT\")     // Recent emails\nseenUIDs, _ := m.GetUIDs(\"SEEN\")         // Read emails\nflaggedUIDs, _ := m.GetUIDs(\"FLAGGED\")   // Starred/flagged emails\n\n// Example output:\nfmt.Printf(\"Found %d total emails\\n\", len(allUIDs))      // Found 342 total emails\nfmt.Printf(\"Found %d unread emails\\n\", len(unseenUIDs))  // Found 12 unread emails\nfmt.Printf(\"UIDs of unread: %v\\n\", unseenUIDs)           // UIDs of unread: [245 246 247 251 252 253 254 255 256 257 258 259]\n\n// Date-based searches\ntodayUIDs, _ := m.GetUIDs(\"ON 15-Sep-2024\")\nsinceUIDs, _ := m.GetUIDs(\"SINCE 10-Sep-2024\")\nbeforeUIDs, _ := m.GetUIDs(\"BEFORE 20-Sep-2024\")\nrangeUIDs, _ := m.GetUIDs(\"SINCE 1-Sep-2024 BEFORE 30-Sep-2024\")\n\n// From/To searches\nfromBossUIDs, _ := m.GetUIDs(`FROM \"boss@company.com\"`)\ntoMeUIDs, _ := m.GetUIDs(`TO \"me@company.com\"`)\n\n// Subject/body searches\nsubjectUIDs, _ := m.GetUIDs(`SUBJECT \"invoice\"`)\nbodyUIDs, _ := m.GetUIDs(`BODY \"payment\"`)\ntextUIDs, _ := m.GetUIDs(`TEXT \"urgent\"`) // Searches both subject and body\n\n// Complex searches\ncomplexUIDs, _ := m.GetUIDs(`UNSEEN FROM \"support@github.com\" SINCE 1-Sep-2024`)\n\n// UID ranges (raw IMAP syntax)\nfirstUID, _ := m.GetUIDs(\"1\")          // UID 1 only\nlastUID, _ := m.GetUIDs(\"*\")           // Highest UID only\nrangeUIDs, _ := m.GetUIDs(\"1:10\")      // UIDs 1 through 10\n\n// Get the N most recent messages (recommended for \"last N\" queries)\nlast10UIDs, _ := m.GetLastNUIDs(10)    // Last 10 messages by UID\n\n// Cheaper method to retrieve the latest UID (requires RFC-4731;\n// not all servers support this — check the error).\nmaxUID, _ := m.GetMaxUID()             // Highest UID only\n\n// Size-based searches\nlargeUIDs, _ := m.GetUIDs(\"LARGER 10485760\")  // Emails larger than 10MB\nsmallUIDs, _ := m.GetUIDs(\"SMALLER 1024\")     // Emails smaller than 1KB\n\n// Non-ASCII searches using RFC 3501 literal syntax\n// The library automatically detects and handles literal syntax {n}\n// where n is the byte count of the following data\n\n// Search for Cyrillic text in subject (тест = 8 bytes in UTF-8)\ncyrillicUIDs, _ := m.GetUIDs(\"CHARSET UTF-8 Subject {8}\\r\\nтест\")\n\n// Search for Chinese text in subject (测试 = 6 bytes in UTF-8)  \nchineseUIDs, _ := m.GetUIDs(\"CHARSET UTF-8 Subject {6}\\r\\n测试\")\n\n// Search for Japanese text in body (テスト = 9 bytes in UTF-8)\njapaneseUIDs, _ := m.GetUIDs(\"CHARSET UTF-8 BODY {9}\\r\\nテスト\")\n\n// Search for Arabic text (اختبار = 12 bytes in UTF-8)\narabicUIDs, _ := m.GetUIDs(\"CHARSET UTF-8 TEXT {12}\\r\\nاختبار\")\n\n// Search with emoji (😀👍 = 8 bytes in UTF-8)\nemojiUIDs, _ := m.GetUIDs(\"CHARSET UTF-8 TEXT {8}\\r\\n😀👍\")\n\n// Note: Always specify CHARSET UTF-8 for non-ASCII searches\n// The {n} syntax tells the server exactly how many bytes to expect\n// This is crucial since Unicode characters use multiple bytes\n```\n\n#### Type-Safe Search Builder\n\nFor complex or repeated queries, use the fluent `SearchBuilder` instead of raw strings:\n\n```go\n// Simple search\nuids, _ := m.SearchUIDs(imap.Search().Unseen())\n\n// Combine multiple criteria (AND)\nuids, _ = m.SearchUIDs(\n    imap.Search().\n        From(\"boss@company.com\").\n        Since(time.Now().AddDate(0, 0, -7)).\n        Unseen(),\n)\n\n// Date range\nlastMonth := time.Now().AddDate(0, -1, 0)\nuids, _ = m.SearchUIDs(\n    imap.Search().Since(lastMonth).Before(time.Now()).Flagged(),\n)\n\n// OR and NOT operators\nuids, _ = m.SearchUIDs(\n    imap.Search().Or(\n        imap.Search().From(\"alice@example.com\"),\n        imap.Search().From(\"bob@example.com\"),\n    ).Unseen(),\n)\n\nuids, _ = m.SearchUIDs(\n    imap.Search().Not(imap.Search().From(\"noreply@\")).Unseen(),\n)\n\n// Size filters\nuids, _ = m.SearchUIDs(imap.Search().Larger(10 * 1024 * 1024)) // \u003e 10MB\n\n// Non-ASCII text is handled automatically (CHARSET UTF-8 + literal syntax)\nuids, _ = m.SearchUIDs(imap.Search().Subject(\"日報\"))\n\n// You can also use Build() to get the raw string for GetUIDs()\nquery := imap.Search().From(\"alice\").Unseen().Since(lastMonth).Build()\nuids, _ = m.GetUIDs(query)\n```\n\n### 3. Fetching Email Details\n\n```go\n// Get overview (headers only, no body) - FAST\noverviews, err := m.GetOverviews(uids...)\nif err != nil { panic(err) }\n\nfor uid, email := range overviews {\n    fmt.Printf(\"UID %d:\\n\", uid)\n    fmt.Printf(\"  Subject: %s\\n\", email.Subject)\n    fmt.Printf(\"  From: %s\\n\", email.From)\n    fmt.Printf(\"  Date: %s\\n\", email.Sent)\n    fmt.Printf(\"  Size: %d bytes\\n\", email.Size)\n    fmt.Printf(\"  Flags: %v\\n\", email.Flags)\n}\n\n// Example output:\n// UID 245:\n//   Subject: Your order has shipped!\n//   From: Amazon \u003cship-confirm@amazon.com\u003e\n//   Date: 2024-09-15 14:23:01 +0000 UTC\n//   Size: 45234 bytes\n//   Flags: [\\Seen]\n\n// Get full emails with bodies - SLOWER\nemails, err := m.GetEmails(uids...)\nif err != nil { panic(err) }\n\nfor uid, email := range emails {\n    fmt.Printf(\"\\n=== Email UID %d ===\\n\", uid)\n    fmt.Printf(\"Subject: %s\\n\", email.Subject)\n    fmt.Printf(\"From: %s\\n\", email.From)\n    fmt.Printf(\"To: %s\\n\", email.To)\n    fmt.Printf(\"CC: %s\\n\", email.CC)\n    fmt.Printf(\"Date Sent: %s\\n\", email.Sent)\n    fmt.Printf(\"Date Received: %s\\n\", email.Received)\n    fmt.Printf(\"Message-ID: %s\\n\", email.MessageID)\n    fmt.Printf(\"Flags: %v\\n\", email.Flags)\n    fmt.Printf(\"Size: %d bytes\\n\", email.Size)\n\n    // Body content\n    if len(email.Text) \u003e 0 {\n        fmt.Printf(\"Text (first 200 chars): %.200s...\\n\", email.Text)\n    }\n    if len(email.HTML) \u003e 0 {\n        fmt.Printf(\"HTML length: %d bytes\\n\", len(email.HTML))\n    }\n\n    // Attachments\n    if len(email.Attachments) \u003e 0 {\n        fmt.Printf(\"Attachments (%d):\\n\", len(email.Attachments))\n        for _, att := range email.Attachments {\n            fmt.Printf(\"  - %s (%s, %d bytes)\\n\",\n                att.Name, att.MimeType, len(att.Content))\n        }\n    }\n}\n\n// Example full output:\n// === Email UID 245 ===\n// Subject: Your order has shipped!\n// From: ship-confirm@amazon.com:Amazon Shipping\n// To: customer@example.com:John Doe\n// CC:\n// Date Sent: 2024-09-15 14:23:01 +0000 UTC\n// Date Received: 2024-09-15 14:23:15 +0000 UTC\n// Message-ID: \u003c20240915142301.3F4A5B0@amazon.com\u003e\n// Flags: [\\Seen]\n// Size: 45234 bytes\n// Text (first 200 chars): Hello John, Your order #123-4567890 has shipped and is on its way! Track your package: ...\n// HTML length: 42150 bytes\n// Attachments (2):\n//   - invoice.pdf (application/pdf, 125432 bytes)\n//   - shipping-label.png (image/png, 85234 bytes)\n\n// Using the String() method for a quick summary\nemail := emails[245]\nfmt.Print(email)\n// Output:\n// Subject: Your order has shipped!\n// To: customer@example.com:John Doe\n// From: ship-confirm@amazon.com:Amazon Shipping\n// Text: Hello John, Your order...(4.5 kB)\n// HTML: \u003chtml xmlns:v=\"urn:s... (42 kB)\n// 2 Attachment(s): [invoice.pdf (application/pdf 125 kB), shipping-label.png (image/png 85 kB)]\n```\n\n### 4. Email Operations\n\n```go\n// === Moving and Copying Emails ===\nuid := 245\nerr = m.MoveEmail(uid, \"INBOX/Archive\")\nif err != nil { panic(err) }\nfmt.Printf(\"Moved email %d to Archive\\n\", uid)\n\n// Copy keeps the original in the current folder\nerr = m.CopyEmail(uid, \"INBOX/Backup\")\nif err != nil { panic(err) }\nfmt.Printf(\"Copied email %d to Backup\\n\", uid)\n\n// === Uploading Messages (APPEND) ===\nmsg := []byte(\"From: me@example.com\\r\\nTo: you@example.com\\r\\nSubject: Hello\\r\\n\\r\\nMessage body\")\nerr = m.Append(\"Drafts\", []string{`\\Draft`, `\\Seen`}, time.Now(), msg)\nif err != nil { panic(err) }\nfmt.Println(\"Uploaded draft message\")\n\n// === Setting Flags ===\n// Mark as read\nerr = m.MarkSeen(uid)\nif err != nil { panic(err) }\n\n// Set multiple flags at once\nflags := imap.Flags{\n    Seen:     imap.FlagAdd,      // Mark as read\n    Flagged:  imap.FlagAdd,      // Star/flag the email\n    Answered: imap.FlagRemove,   // Remove answered flag\n}\nerr = m.SetFlags(uid, flags)\nif err != nil { panic(err) }\n\n// Custom keywords (if server supports)\nflags = imap.Flags{\n    Keywords: map[string]bool{\n        \"$Important\": true,      // Add custom keyword\n        \"$Processed\": true,      // Add another\n        \"$Pending\":   false,     // Remove this keyword\n    },\n}\nerr = m.SetFlags(uid, flags)\nif err != nil { panic(err) }\n\n// === Deleting Emails ===\n// Step 1: Mark as deleted (sets \\Deleted flag)\nerr = m.DeleteEmail(uid)\nif err != nil { panic(err) }\nfmt.Printf(\"Marked email %d for deletion\\n\", uid)\n\n// Step 2: Expunge to permanently remove all \\Deleted emails\nerr = m.Expunge()\nif err != nil { panic(err) }\nfmt.Println(\"Permanently deleted all marked emails\")\n\n// Note: Some servers support UID EXPUNGE for selective expunge\n// This library uses regular EXPUNGE which removes ALL \\Deleted messages\n```\n\n### 5. IDLE Notifications (Real-time Updates)\n\n```go\n// IDLE allows you to receive real-time notifications when mailbox changes occur\n// The connection will automatically refresh IDLE every 5 minutes (RFC requirement)\n\n// Create an event handler\nhandler := \u0026imap.IdleHandler{\n    // New email arrived\n    OnExists: func(e imap.ExistsEvent) {\n        fmt.Printf(\"[EXISTS] New message at index: %d\\n\", e.MessageIndex)\n        // Example output: [EXISTS] New message at index: 343\n\n        // You might want to fetch the new email:\n        // uids, _ := m.GetUIDs(fmt.Sprintf(\"%d\", e.MessageIndex))\n        // emails, _ := m.GetEmails(uids...)\n    },\n\n    // Email was deleted/expunged\n    OnExpunge: func(e imap.ExpungeEvent) {\n        fmt.Printf(\"[EXPUNGE] Message removed at index: %d\\n\", e.MessageIndex)\n        // Example output: [EXPUNGE] Message removed at index: 125\n    },\n\n    // Email flags changed (read, flagged, etc.)\n    OnFetch: func(e imap.FetchEvent) {\n        fmt.Printf(\"[FETCH] Flags changed - Index: %d, UID: %d, Flags: %v\\n\",\n            e.MessageIndex, e.UID, e.Flags)\n        // Example output: [FETCH] Flags changed - Index: 42, UID: 245, Flags: [\\Seen \\Flagged]\n    },\n}\n\n// Start IDLE (non-blocking, runs in background)\nerr := m.StartIdle(handler)\nif err != nil { panic(err) }\n\n// Your application continues running...\n// IDLE events will be handled in the background\n\n// When you're done, stop IDLE\nerr = m.StopIdle()\nif err != nil { panic(err) }\n\n// Full example with proper lifecycle:\nfunc monitorInbox(m *imap.Dialer) {\n    // Select the folder to monitor\n    if err := m.SelectFolder(\"INBOX\"); err != nil {\n        panic(err)\n    }\n\n    handler := \u0026imap.IdleHandler{\n        OnExists: func(e imap.ExistsEvent) {\n            fmt.Printf(\"📬 New email! Total messages now: %d\\n\", e.MessageIndex)\n        },\n        OnExpunge: func(e imap.ExpungeEvent) {\n            fmt.Printf(\"🗑️ Email deleted at position %d\\n\", e.MessageIndex)\n        },\n        OnFetch: func(e imap.FetchEvent) {\n            fmt.Printf(\"📝 Email %d updated with flags: %v\\n\", e.UID, e.Flags)\n        },\n    }\n\n    fmt.Println(\"Starting IDLE monitoring...\")\n    if err := m.StartIdle(handler); err != nil {\n        panic(err)\n    }\n\n    // Monitor for 30 minutes\n    time.Sleep(30 * time.Minute)\n\n    fmt.Println(\"Stopping IDLE monitoring...\")\n    if err := m.StopIdle(); err != nil {\n        panic(err)\n    }\n}\n```\n\n### 6. Error Handling and Reconnection\n\n```go\n// The library automatically handles reconnection for most operations\n// But here's how to handle errors properly:\n\nfunc robustEmailFetch(m *imap.Dialer) {\n    // Set retry configuration\n    imap.RetryCount = 5  // Will retry failed operations 5 times\n    imap.Verbose = true  // Emit debug logs while retrying commands\n\n    err := m.SelectFolder(\"INBOX\")\n    if err != nil {\n        // Connection errors are automatically retried\n        // This only fails after all retries are exhausted\n        fmt.Printf(\"Failed to select folder after %d retries: %v\\n\", imap.RetryCount, err)\n\n        // You might want to manually reconnect\n        if err := m.Reconnect(); err != nil {\n            fmt.Printf(\"Manual reconnection failed: %v\\n\", err)\n            return\n        }\n    }\n\n    // Fetch emails with automatic retry on network issues\n    uids, err := m.GetUIDs(\"UNSEEN\")\n    if err != nil {\n        fmt.Printf(\"Search failed: %v\\n\", err)\n        return\n    }\n\n    // The library will automatically:\n    // 1. Close the broken connection\n    // 2. Create a new connection\n    // 3. Re-authenticate (LOGIN or XOAUTH2)\n    // 4. Re-select the previously selected folder\n    // 5. Retry the failed command\n\n    emails, err := m.GetEmails(uids...)\n    if err != nil {\n        fmt.Printf(\"Fetch failed after retries: %v\\n\", err)\n        return\n    }\n\n    fmt.Printf(\"Successfully fetched %d emails\\n\", len(emails))\n}\n\n// Timeout configuration\nfunc configureTimeouts() {\n    // Connection timeout (for initial connection)\n    imap.DialTimeout = 10 * time.Second\n\n    // Command timeout (for each IMAP command)\n    imap.CommandTimeout = 30 * time.Second\n\n    // Now commands will timeout if they take too long\n    m, err := imap.New(\"user\", \"pass\", \"mail.server.com\", 993)\n    if err != nil {\n        // Connection failed within 10 seconds\n        panic(err)\n    }\n    defer m.Close()\n\n    // This search will timeout after 30 seconds\n    uids, err := m.GetUIDs(\"ALL\")\n    if err != nil {\n        fmt.Printf(\"Command timed out or failed: %v\\n\", err)\n    }\n}\n```\n\n### 7. Complete Working Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"time\"\n\n    imap \"github.com/BrianLeishman/go-imap\"\n)\n\nfunc main() {\n    // Configure the library\n    imap.Verbose = false\n    imap.RetryCount = 3\n    imap.DialTimeout = 10 * time.Second\n    imap.CommandTimeout = 30 * time.Second\n\n    // Connect\n    fmt.Println(\"Connecting to IMAP server...\")\n    m, err := imap.New(\"your-email@gmail.com\", \"your-password\", \"imap.gmail.com\", 993)\n    if err != nil {\n        log.Fatalf(\"Connection failed: %v\", err)\n    }\n    defer m.Close()\n\n    // List folders\n    fmt.Println(\"\\n📁 Available folders:\")\n    folders, err := m.GetFolders()\n    if err != nil {\n        log.Fatalf(\"Failed to get folders: %v\", err)\n    }\n    for _, folder := range folders {\n        fmt.Printf(\"  - %s\\n\", folder)\n    }\n\n    // Select INBOX\n    fmt.Println(\"\\n📥 Selecting INBOX...\")\n    if err := m.SelectFolder(\"INBOX\"); err != nil {\n        log.Fatalf(\"Failed to select INBOX: %v\", err)\n    }\n\n    // Get unread emails\n    fmt.Println(\"\\n🔍 Searching for unread emails...\")\n    unreadUIDs, err := m.GetUIDs(\"UNSEEN\")\n    if err != nil {\n        log.Fatalf(\"Search failed: %v\", err)\n    }\n    fmt.Printf(\"Found %d unread emails\\n\", len(unreadUIDs))\n\n    // Fetch first 5 unread (or less)\n    limit := 5\n    if len(unreadUIDs) \u003c limit {\n        limit = len(unreadUIDs)\n    }\n\n    if limit \u003e 0 {\n        fmt.Printf(\"\\n📧 Fetching first %d unread emails...\\n\", limit)\n        emails, err := m.GetEmails(unreadUIDs[:limit]...)\n        if err != nil {\n            log.Fatalf(\"Failed to fetch emails: %v\", err)\n        }\n\n        for uid, email := range emails {\n            fmt.Printf(\"\\n--- Email UID %d ---\\n\", uid)\n            fmt.Printf(\"From: %s\\n\", email.From)\n            fmt.Printf(\"Subject: %s\\n\", email.Subject)\n            fmt.Printf(\"Date: %s\\n\", email.Sent.Format(\"Jan 2, 2006 3:04 PM\"))\n            fmt.Printf(\"Size: %.1f KB\\n\", float64(email.Size)/1024)\n\n            if len(email.Text) \u003e 100 {\n                fmt.Printf(\"Preview: %.100s...\\n\", email.Text)\n            } else if len(email.Text) \u003e 0 {\n                fmt.Printf(\"Preview: %s\\n\", email.Text)\n            }\n\n            if len(email.Attachments) \u003e 0 {\n                fmt.Printf(\"Attachments: %d\\n\", len(email.Attachments))\n                for _, att := range email.Attachments {\n                    fmt.Printf(\"  - %s (%.1f KB)\\n\", att.Name, float64(len(att.Content))/1024)\n                }\n            }\n\n            // Mark first email as read\n            if uid == unreadUIDs[0] {\n                fmt.Printf(\"\\n✓ Marking email %d as read...\\n\", uid)\n                if err := m.MarkSeen(uid); err != nil {\n                    fmt.Printf(\"Failed to mark as read: %v\\n\", err)\n                }\n            }\n        }\n    }\n\n    // Get some statistics\n    fmt.Println(\"\\n📊 Mailbox Statistics:\")\n    allUIDs, _ := m.GetUIDs(\"ALL\")\n    seenUIDs, _ := m.GetUIDs(\"SEEN\")\n    flaggedUIDs, _ := m.GetUIDs(\"FLAGGED\")\n\n    fmt.Printf(\"  Total emails: %d\\n\", len(allUIDs))\n    fmt.Printf(\"  Read emails: %d\\n\", len(seenUIDs))\n    fmt.Printf(\"  Unread emails: %d\\n\", len(allUIDs)-len(seenUIDs))\n    fmt.Printf(\"  Flagged emails: %d\\n\", len(flaggedUIDs))\n\n    // Start IDLE monitoring for 10 seconds\n    fmt.Println(\"\\n👀 Monitoring for new emails (10 seconds)...\")\n    handler := \u0026imap.IdleHandler{\n        OnExists: func(e imap.ExistsEvent) {\n            fmt.Printf(\"  📬 New email arrived! (message #%d)\\n\", e.MessageIndex)\n        },\n    }\n\n    if err := m.StartIdle(handler); err == nil {\n        time.Sleep(10 * time.Second)\n        _ = m.StopIdle()\n    }\n\n    fmt.Println(\"\\n✅ Done!\")\n}\n\n/* Example Output:\n\nConnecting to IMAP server...\n\n📁 Available folders:\n  - INBOX\n  - Sent\n  - Drafts\n  - Trash\n  - [Gmail]/All Mail\n  - [Gmail]/Spam\n  - [Gmail]/Starred\n  - [Gmail]/Important\n\n📥 Selecting INBOX...\n\n🔍 Searching for unread emails...\nFound 3 unread emails\n\n📧 Fetching first 3 unread emails...\n\n--- Email UID 1247 ---\nFrom: notifications@github.com:GitHub\nSubject: [org/repo] New issue: Bug in authentication flow (#123)\nDate: Nov 11, 2024 2:15 PM\nSize: 8.5 KB\nPreview: User johndoe opened an issue: When trying to authenticate with OAuth2, the system returns a 401 error even with valid...\nAttachments: 0\n\n✓ Marking email 1247 as read...\n\n--- Email UID 1248 ---\nFrom: team@company.com:Team Update\nSubject: Weekly Team Sync - Meeting Notes\nDate: Nov 11, 2024 3:30 PM\nSize: 12.3 KB\nPreview: Hi team, Here are the notes from today's sync: 1. Project Alpha is on track for Dec release 2. Need volunteers for...\nAttachments: 1\n  - meeting-notes.pdf (156.2 KB)\n\n--- Email UID 1249 ---\nFrom: noreply@service.com:Service Alert\nSubject: Your monthly report is ready\nDate: Nov 11, 2024 4:45 PM\nSize: 45.6 KB\nPreview: Your monthly usage report for October 2024 is now available. View it in your dashboard or download the attached PDF...\nAttachments: 2\n  - october-report.pdf (523.1 KB)\n  - usage-chart.png (89.3 KB)\n\n📊 Mailbox Statistics:\n  Total emails: 1532\n  Read emails: 1530\n  Unread emails: 2\n  Flagged emails: 23\n\n👀 Monitoring for new emails (10 seconds)...\n\n✅ Done!\n*/\n```\n\n## Reconnect Behavior\n\nWhen a command fails, the library closes the socket, reconnects, re‑authenticates (LOGIN or XOAUTH2), and restores the previously selected folder. You can tune retry count via `imap.RetryCount`.\n\n## TLS \u0026 Certificates\n\nConnections are TLS by default. For servers with self‑signed certs you can set `imap.TLSSkipVerify = true`, but be aware this disables certificate validation and can expose you to man‑in‑the‑middle attacks. Prefer real certificates in production.\n\n## Server Compatibility\n\nTested against common providers such as Gmail and Office 365/Exchange. The client targets RFC 3501 and common extensions used for search, fetch, and move.\n\n## CI \u0026 Quality\n\nThis repo runs Go 1.25.1+ on CI with vet and race‑enabled tests. We also track documentation on pkg.go.dev and Go Report Card.\n\n## Contributing\n\nIssues and PRs are welcome! If adding public APIs, please include short docs and examples. Make sure `go vet` and `go test -race ./...` pass locally.\n\n## License\n\nMIT © Brian Leishman\n\n---\n\n### Built With\n\n- [jhillyerd/enmime](https://github.com/jhillyerd/enmime) – MIME parsing\n- [dustin/go-humanize](https://github.com/dustin/go-humanize) – Human‑friendly sizes\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBrianLeishman%2Fgo-imap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FBrianLeishman%2Fgo-imap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBrianLeishman%2Fgo-imap/lists"}