diff options
| author | Arpit Chakladar <arpit@chakladar.me> | 2026-09-30 03:09:58 +0530 |
|---|---|---|
| committer | Arpit Chakladar <arpit@chakladar.me> | 2026-09-30 03:09:58 +0530 |
| commit | 77becc9d521490a71560fa87ee33e4f600839cfd (patch) | |
| tree | 95659d5273b773012dd9ac34358d69e60f2c9b9b | |
| parent | fe64e34d2451e33623632c4a4ee216101d18dcc5 (diff) | |
| download | home-manager-config-77becc9d521490a71560fa87ee33e4f600839cfd.tar.gz home-manager-config-77becc9d521490a71560fa87ee33e4f600839cfd.zip | |
feat(communication/neomutt): reworked the email syncing
Now we have the ability to map each folder on the remote server with a
folder with an arbitrary name in the local server. Each folder gets its
own channel with quick and full variations where the quick only download
the new emails from the remote to the local and full makes a sync both
ways.
| -rw-r--r-- | modules/communication/neomutt/.neomuttrc | 20 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/.neomuttrc | 4 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/default.nix | 36 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/flavors/gmail/.neomuttrc | 2 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/flavors/gmail/default.nix | 28 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/options.nix | 8 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/sync-channel.nix | 11 | ||||
| -rw-r--r-- | modules/communication/neomutt/default.nix | 52 | ||||
| -rw-r--r-- | modules/communication/neomutt/lib.nix | 18 | ||||
| -rw-r--r-- | modules/communication/neomutt/mail/mbsync.nix | 89 |
10 files changed, 91 insertions, 177 deletions
diff --git a/modules/communication/neomutt/.neomuttrc b/modules/communication/neomutt/.neomuttrc index bc6779a..dee49a8 100644 --- a/modules/communication/neomutt/.neomuttrc +++ b/modules/communication/neomutt/.neomuttrc @@ -1,33 +1,20 @@ -# Read messages in Neovim. `-` makes Neovim read the message from -# stdin and -R prevents accidental edits to message content.\ - -# Return directly to the index when Neovim exits and do not prepend -# NeoMutt’s pager status line to the message passed to Neovim. set noprompt_after set pager_format = "" -# Base16 OneDark Dark terminal colors for neomutt -# Each column in the index receives a distinct color to keep the UI organized. set menu_scroll = yes set pager_stop = yes -# Enable custom From headers and ask for confirmation/editing set use_from = yes set reverse_name = yes set autoedit = no set edit_headers = yes -# cancel any prompt/command-line entry with Esc, vim-style set abort_key = "<Esc>" -# attachments: use our mailcap for dedicated openers, -# and auto-render html/images inline where sensible set mailcap_path = "~/.config/neomutt/mailcap" auto_view text/plain alternative_order text/html image/png image/jpeg image/gif application/pdf -# Nerd Font icon-based flag/attachment/crypto indicators -# order: new, old, deleted, flagged, replied, old(unused), tagged, encrypt, ... set flag_chars = "" set to_chars = " " set crypt_chars = " " @@ -35,10 +22,8 @@ set attach_format = "%u%D%I %t%4n %T%.40d %> [%m/%M, %e%?C?, %C?, %s] " set index_format = " %5C │ %Z │ %{%d/%m/%y %H:%M} │ %-25.25a │ %s%* " -# Hide the keybindings help bar at the top to save vertical space -set help = no +set help = yes -# Status bar with Nerd Font icons per field set status_format = " Unread: %5u │ New: %5n │ Tagged: %5t │ Filter: %V │ Sorted By: %s " # --- Basic UI Colors -------------------------------------------------- @@ -62,7 +47,6 @@ color tree magenta default # base03 (Thread arrows) color index white default # base05 # --- Index Columns (The Multi-Color Table) ---------------------------- -# Applies distinct colors to specific columns for ALL messages (~A) color index_number cyan default "~A" # base05 (Fixed: was brightblack) color index_date green default "~A" # base0B (Green) color index_flags red default "~A" # base08 (Red) @@ -75,8 +59,6 @@ color index_tag cyan default # base0C (Message tags, %G) color index_tags cyan default # base0C (Transformed tags, %g %J) # --- Index Message States --------------------------------------------- -# By targeting *only* `index_subject`, you preserve your multi-color -# columns while still easily identifying unread or flagged emails. color index_subject blue default "~U" # Unread: Blue (base0D) color index_subject blue default "~N" # New: Blue (base0D) color index_subject red default "~F" # Flagged: Red (base08) diff --git a/modules/communication/neomutt/account/.neomuttrc b/modules/communication/neomutt/account/.neomuttrc index 2f8c9f2..37dc9aa 100644 --- a/modules/communication/neomutt/account/.neomuttrc +++ b/modules/communication/neomutt/account/.neomuttrc @@ -1,2 +1,2 @@ -macro index,pager gs "<enter-command>set my_wait_key=$wait_key wait_key=no<enter><sync-mailbox><shell-escape>neomutt-sync @@QUICK_CHANNEL@@<enter><sync-mailbox><enter-command>set wait_key=$my_wait_key<enter>" -macro index,pager gS "<enter-command>set my_wait_key=$wait_key wait_key=no<enter><sync-mailbox><shell-escape>neomutt-sync @@FULL_CHANNEL@@<enter><sync-mailbox><enter-command>set wait_key=$my_wait_key<enter>" +macro index,pager gs "<enter-command>set my_wait_key=$wait_key wait_key=no<enter><sync-mailbox><shell-escape>neomutt-sync @@QUICK_GROUP@@<enter><sync-mailbox><enter-command>set wait_key=$my_wait_key<enter>" +macro index,pager gS "<enter-command>set my_wait_key=$wait_key wait_key=no<enter><sync-mailbox><shell-escape>neomutt-sync @@FULL_GROUP@@<enter><sync-mailbox><enter-command>set wait_key=$my_wait_key<enter>" diff --git a/modules/communication/neomutt/account/default.nix b/modules/communication/neomutt/account/default.nix index 07a4b89..1d8046a 100644 --- a/modules/communication/neomutt/account/default.nix +++ b/modules/communication/neomutt/account/default.nix @@ -2,6 +2,7 @@ { config, lib, ... }: let cfg = config.communication.neomutt; + mbsyncGroupNames = (import ../lib.nix { inherit lib; }).mbsyncGroupNames; in { imports = [ @@ -47,10 +48,10 @@ in inherit (accountInformation.folders) inbox trash; } // lib.optionalAttrs (accountInformation.folders.drafts != null) { - drafts = accountInformation.folders.drafts; + inherit (accountInformation.folders) drafts; } // lib.optionalAttrs (accountInformation.folders.sent != null) { - sent = accountInformation.folders.sent; + inherit (accountInformation.folders) sent; }; neomutt = { @@ -62,14 +63,43 @@ in mbsync = { enable = true; create = accountInformation.mbsync.create; + expunge = "both"; patterns = accountInformation.mbsync.patterns; flatten = accountInformation.mbsync.flatten; extraConfig = { - account = accountInformation.mbsync.extraConfig.account; + account = { + PipelineDepth = 50; + } + // accountInformation.mbsync.extraConfig.account; channel = accountInformation.mbsync.extraConfig.channel; local = accountInformation.mbsync.extraConfig.local; remote = accountInformation.mbsync.extraConfig.remote; }; + + groups = + let + group = mbsyncGroupNames accountName; + + mkChannels = + sync: + lib.mapAttrs (local: remote: { + farPattern = remote; + nearPattern = local; + extraConfig = { + Create = "Near"; + Expunge = "Both"; + Sync = sync; + SyncState = "*"; + }; + }) accountInformation.mbsync.folders.map; + in + { + "${group.full}".channels = mkChannels "All"; + "${group.quick}".channels = mkChannels [ + "Pull" + "New" + ]; + }; }; notmuch = { diff --git a/modules/communication/neomutt/account/flavors/gmail/.neomuttrc b/modules/communication/neomutt/account/flavors/gmail/.neomuttrc index c87bba0..2106b65 100644 --- a/modules/communication/neomutt/account/flavors/gmail/.neomuttrc +++ b/modules/communication/neomutt/account/flavors/gmail/.neomuttrc @@ -1,2 +1,2 @@ -mailboxes =Inbox ="[Gmail]/Drafts" ="[Gmail]/Sent Mail" ="[Gmail]/Spam" ="[Gmail]/Trash" +mailboxes =inbox =drafts =sent =spam =trash unset record diff --git a/modules/communication/neomutt/account/flavors/gmail/default.nix b/modules/communication/neomutt/account/flavors/gmail/default.nix index b0b7733..ea696b2 100644 --- a/modules/communication/neomutt/account/flavors/gmail/default.nix +++ b/modules/communication/neomutt/account/flavors/gmail/default.nix @@ -8,25 +8,25 @@ { config = lib.mkIf (config.flavor == "gmail.com") { folders = { - inbox = lib.mkDefault "Inbox"; - drafts = lib.mkDefault "[Gmail]/Drafts"; - sent = lib.mkDefault "[Gmail]/Sent Mail"; - trash = lib.mkDefault "[Gmail]/Trash"; + inbox = lib.mkDefault "inbox"; + drafts = lib.mkDefault "drafts"; + sent = lib.mkDefault "sent"; + trash = lib.mkDefault "trash"; }; mbsync = { create = lib.mkDefault "maildir"; - patterns = lib.mkDefault [ - "INBOX" - "[Gmail]/Drafts" - "[Gmail]/Sent Mail" - "[Gmail]/Spam" - ]; + patterns = lib.mkDefault [ "INBOX" ]; + folders.map = lib.mkDefault { + inbox = "INBOX"; + drafts = "[Gmail]/Drafts"; + sent = "[Gmail]/Sent Mail"; + spam = "[Gmail]/Spam"; + trash = "[Gmail]/Trash"; + }; extraConfig.channel = { Sync = lib.mkDefault "All"; CopyArrivalDate = lib.mkDefault "yes"; - Expunge = lib.mkDefault "Both"; - SyncState = lib.mkDefault "*"; }; }; @@ -34,8 +34,8 @@ notmuch.neomutt.virtual-mailboxes = lib.mkDefault [ { - name = "All Mail"; - query = "folder:${name}/Inbox or folder:\"${name}/[Gmail]/Sent Mail\""; + name = "all"; + query = "folder:${name}/inbox or folder:\"${name}/sent\""; } ]; }; diff --git a/modules/communication/neomutt/account/options.nix b/modules/communication/neomutt/account/options.nix index 676f8b0..3090db2 100644 --- a/modules/communication/neomutt/account/options.nix +++ b/modules/communication/neomutt/account/options.nix @@ -121,6 +121,14 @@ description = "Flatten hierarchy delimiter."; }; + folders = { + map = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Local folder name -> remote folder name, for folders that should be renamed locally."; + }; + }; + extraConfig = { account = lib.mkOption { type = lib.types.attrsOf ( diff --git a/modules/communication/neomutt/account/sync-channel.nix b/modules/communication/neomutt/account/sync-channel.nix index 2252594..cb966c8 100644 --- a/modules/communication/neomutt/account/sync-channel.nix +++ b/modules/communication/neomutt/account/sync-channel.nix @@ -5,20 +5,19 @@ ... }: let - - mbsyncNamesFromName = (import ../lib.nix { inherit lib; }).mbsyncNamesFromName; + mbsyncGroupNames = (import ../lib.nix { inherit lib; }).mbsyncGroupNames; in { config = lib.mkIf config.enable { neomutt.extraConfig = let - mbsyncNames = mbsyncNamesFromName name; + group = mbsyncGroupNames name; in builtins.replaceStrings - [ "@@QUICK_CHANNEL@@" "@@FULL_CHANNEL@@" ] + [ "@@QUICK_GROUP@@" "@@FULL_GROUP@@" ] [ - mbsyncNames.channels.quick - mbsyncNames.channels.full + group.quick + group.full ] (builtins.readFile ./.neomuttrc); }; diff --git a/modules/communication/neomutt/default.nix b/modules/communication/neomutt/default.nix index 6189b23..40c8950 100644 --- a/modules/communication/neomutt/default.nix +++ b/modules/communication/neomutt/default.nix @@ -8,16 +8,7 @@ let cfg = config.communication.neomutt; icons = config.desktop.icons.apps; - - mbsyncNamesFromName = (import ./lib.nix { inherit lib; }).mbsyncNamesFromName; - - fullSyncChannels = lib.mapAttrsToList (name: account: (mbsyncNamesFromName name).channels.full) ( - lib.filterAttrs (name: account: account.enable) cfg.accounts - ); - - quickSyncChannels = lib.mapAttrsToList (name: account: (mbsyncNamesFromName name).channels.quick) ( - lib.filterAttrs (name: account: account.enable) cfg.accounts - ); + mbsyncGroupNames = (import ./lib.nix { inherit lib; }).mbsyncGroupNames; neomuttSyncScript = pkgs.writeShellApplication { name = "neomutt-sync"; @@ -34,27 +25,26 @@ let text = builtins.readFile ./neomutt-sync.sh; }; - neomuttSyncDaemon = pkgs.writeShellApplication { - name = "neomutt-sync-daemon"; - runtimeInputs = [ - config.terminal.bash.package - pkgs.coreutils - pkgs.procps - config.programs.mbsync.package - config.programs.notmuch.package - ]; - text = - builtins.replaceStrings - [ - "@@QUICK_CHANNELS@@" - "@@FULL_CHANNELS@@" - ] - [ - (lib.concatStringsSep "," quickSyncChannels) - (lib.concatStringsSep "," fullSyncChannels) - ] - (builtins.readFile ./neomutt-sync-daemon.sh); - }; + neomuttSyncDaemon = + let + accountNames = lib.attrNames (lib.filterAttrs (_: a: a.enable) cfg.accounts); + groupsFor = kind: lib.concatStringsSep "," (map (n: (mbsyncGroupNames n).${kind}) accountNames); + in + pkgs.writeShellApplication { + name = "neomutt-sync-daemon"; + runtimeInputs = [ + config.terminal.bash.package + pkgs.coreutils + pkgs.procps + config.programs.mbsync.package + config.programs.notmuch.package + ]; + text = + lib.replaceStrings + [ "@@QUICK_GROUPS@@" "@@FULL_GROUPS@@" ] + [ (groupsFor "quick") (groupsFor "full") ] + (builtins.readFile ./neomutt-sync-daemon.sh); + }; neomuttLauncher = pkgs.writeShellApplication { name = "neomutt-launcher"; diff --git a/modules/communication/neomutt/lib.nix b/modules/communication/neomutt/lib.nix index 253ada8..7b85e5e 100644 --- a/modules/communication/neomutt/lib.nix +++ b/modules/communication/neomutt/lib.nix @@ -1,20 +1,14 @@ # some basic common utility functions for neomutt { lib, ... }: { - mbsyncNamesFromName = - name: + mbsyncGroupNames = + accountName: let - channelNameBase = lib.replaceStrings [ "@" "." ] [ "-" "_" ] name; + base = lib.replaceStrings [ "@" "." ] [ "-" "_" ] accountName; in { - base = channelNameBase; - channels = { - full = "${channelNameBase}-full"; - quick = "${channelNameBase}-quick"; - }; - servers = { - remote = "${channelNameBase}-remote"; - local = "${channelNameBase}-local"; - }; + inherit base; + full = "${base}-full"; + quick = "${base}-quick"; }; } diff --git a/modules/communication/neomutt/mail/mbsync.nix b/modules/communication/neomutt/mail/mbsync.nix index b93b134..66964e0 100644 --- a/modules/communication/neomutt/mail/mbsync.nix +++ b/modules/communication/neomutt/mail/mbsync.nix @@ -6,98 +6,9 @@ }: let cfg = config.communication.neomutt; - - mbsyncNamesFromName = (import ../lib.nix { inherit lib; }).mbsyncNamesFromName; - - mbsyncValue = - value: - if lib.isList value then - lib.concatStringsSep " " (map mbsyncValue value) - else if lib.isBool value then - lib.hm.booleans.yesNo value - else if lib.isInt value then - toString value - else if lib.isString value then - if builtins.match ".* .*" value != null then ''"${lib.escape [ ''"'' ] value}"'' else value - else - throw "Unsupported mbsync value"; - - mbsyncSection = name: attrs: '' - ${name} - ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n} ${mbsyncValue v}") attrs)} - ''; - - mbsyncAccount = - account: - let - mbsyncNames = mbsyncNamesFromName account.name; - in - lib.concatStringsSep "\n" [ - (mbsyncSection "IMAPAccount ${mbsyncNames.base}" ( - { - Host = account.imap.host; - User = account.userName; - PipelineDepth = 50; - TLSType = - if !account.imap.tls.enable then - "None" - else if account.imap.tls.useStartTls then - "STARTTLS" - else - "IMAPS"; - } - // lib.optionalAttrs (account.passwordCommand != null) { - PassCmd = toString account.passwordCommand; - } - // lib.optionalAttrs (account.imap.tls.certificatesFile != null) { - CertificateFile = toString account.imap.tls.certificatesFile; - } - // lib.optionalAttrs (account.imap.port != null) { - Port = account.imap.port; - } - )) - - (mbsyncSection "IMAPStore ${mbsyncNames.servers.remote}" { - Account = mbsyncNames.base; - }) - - (mbsyncSection "MaildirStore ${mbsyncNames.servers.local}" { - Path = "${account.maildir.absPath}/"; - Inbox = "${account.maildir.absPath}/${account.folders.inbox}"; - SubFolders = "Verbatim"; - }) - - (mbsyncSection "Channel ${mbsyncNames.channels.quick}" { - Far = ":${mbsyncNames.servers.remote}:\"INBOX\""; - Near = ":${mbsyncNames.servers.local}:\"INBOX\""; - Create = "Near"; - Expunge = "None"; - Sync = [ - "Pull" - "New" - ]; - SyncState = "*"; - }) - - (mbsyncSection "Channel ${mbsyncNames.channels.full}" { - Far = ":${mbsyncNames.servers.remote}:"; - Near = ":${mbsyncNames.servers.local}:"; - Patterns = account.mbsync.patterns; - Create = "Near"; - Expunge = "Both"; - Sync = "All"; - SyncState = "*"; - }) - ]; - - accounts = lib.filter (account: account.enable && account.mbsync.enable) ( - lib.attrValues config.accounts.email.accounts - ); in { config = lib.mkIf cfg.enable { programs.mbsync.enable = true; - - xdg.configFile."mbsync/.mbsyncrc".text = lib.concatStringsSep "\n" (map mbsyncAccount accounts); }; } |
