aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArpit Chakladar <arpit@chakladar.me>2026-09-30 03:09:58 +0530
committerArpit Chakladar <arpit@chakladar.me>2026-09-30 03:09:58 +0530
commit77becc9d521490a71560fa87ee33e4f600839cfd (patch)
tree95659d5273b773012dd9ac34358d69e60f2c9b9b
parentfe64e34d2451e33623632c4a4ee216101d18dcc5 (diff)
downloadhome-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/.neomuttrc20
-rw-r--r--modules/communication/neomutt/account/.neomuttrc4
-rw-r--r--modules/communication/neomutt/account/default.nix36
-rw-r--r--modules/communication/neomutt/account/flavors/gmail/.neomuttrc2
-rw-r--r--modules/communication/neomutt/account/flavors/gmail/default.nix28
-rw-r--r--modules/communication/neomutt/account/options.nix8
-rw-r--r--modules/communication/neomutt/account/sync-channel.nix11
-rw-r--r--modules/communication/neomutt/default.nix52
-rw-r--r--modules/communication/neomutt/lib.nix18
-rw-r--r--modules/communication/neomutt/mail/mbsync.nix89
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);
};
}