aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules/communication/default.nix1
-rw-r--r--modules/communication/email/default.nix30
-rw-r--r--modules/communication/neomutt/account/default.nix106
-rw-r--r--modules/communication/neomutt/account/flavors/gmail.nix45
-rw-r--r--modules/communication/neomutt/account/options.nix328
-rw-r--r--modules/communication/neomutt/assertions.nix7
-rw-r--r--modules/communication/neomutt/default.nix429
-rw-r--r--modules/communication/neomutt/neomutt.nix423
-rw-r--r--modules/communication/neomutt/sync/default.nix8
-rw-r--r--modules/communication/neomutt/sync/mbsync.nix9
-rw-r--r--modules/communication/neomutt/sync/notmuch.nix9
-rw-r--r--modules/communication/neomutt/theme.nix4
-rw-r--r--modules/private/email.example.nix49
-rw-r--r--users/arpit.nix1
14 files changed, 942 insertions, 507 deletions
diff --git a/modules/communication/default.nix b/modules/communication/default.nix
index ade90bb..cd8f0d6 100644
--- a/modules/communication/default.nix
+++ b/modules/communication/default.nix
@@ -2,7 +2,6 @@
{
imports = [
- ./email
./neomutt
];
}
diff --git a/modules/communication/email/default.nix b/modules/communication/email/default.nix
deleted file mode 100644
index 969403d..0000000
--- a/modules/communication/email/default.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- config,
- lib,
- ...
-}:
-
-let
- mailDir = "${config.home.homeDirectory}/.local/share/mail";
-in
-{
- options.communication.email = {
- enable = lib.mkEnableOption "Email suite (mbsync + notmuch)";
-
- accounts = lib.mkOption {
- type = lib.types.attrsOf lib.types.anything;
- default = { };
- description = "Email accounts. Each attribute name is an account identifier.";
- };
- };
-
- config = lib.mkIf config.communication.email.enable {
- programs.mbsync.enable = true;
- programs.notmuch.enable = true;
-
- accounts.email.maildirBasePath = mailDir;
- accounts.email.accounts = config.communication.email.accounts;
-
- home.sessionVariables.MAILDIR = mailDir;
- };
-}
diff --git a/modules/communication/neomutt/account/default.nix b/modules/communication/neomutt/account/default.nix
new file mode 100644
index 0000000..183e5ff
--- /dev/null
+++ b/modules/communication/neomutt/account/default.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+ inherit (lib) types;
+in
+{
+ options.communication.neomutt.accounts = mkOption {
+ type = types.attrsOf (
+ types.submodule {
+ imports = [
+ ./options.nix
+ ./flavors/gmail.nix
+ ];
+ }
+ );
+ default = { };
+ description = "Specification of email accounts.";
+ };
+
+ config = mkIf config.communication.neomutt.enable {
+ accounts.email.accounts = mapAttrs (
+ name: account:
+ let
+ a = account;
+ in
+ {
+ inherit (a) realName address primary;
+ userName = if a.userName != null then a.userName else a.address;
+
+ passwordCommand =
+ if a.passwordGopassSecret != null then
+ "${lib.getExe config.security.gopass.package} -o ${a.passwordGopassSecret}"
+ else
+ null;
+
+ flavor = a.flavor;
+ inherit (a) aliases;
+
+ folders = {
+ inherit (a.folders) inbox trash;
+ }
+ // optionalAttrs (a.folders.drafts != null) { drafts = a.folders.drafts; }
+ // optionalAttrs (a.folders.sent != null) { sent = a.folders.sent; };
+
+ neomutt = {
+ enable = true;
+ mailboxType = a.neomutt.mailboxType;
+ extraConfig = a.neomutt.extraConfig;
+ };
+
+ mbsync = {
+ enable = true;
+ create = a.mbsync.create;
+ patterns = a.mbsync.patterns;
+ flatten = a.mbsync.flatten;
+ extraConfig = {
+ account = a.mbsync.extraConfig.account;
+ channel = a.mbsync.extraConfig.channel;
+ local = a.mbsync.extraConfig.local;
+ remote = a.mbsync.extraConfig.remote;
+ };
+ };
+
+ notmuch = {
+ enable = true;
+ neomutt = {
+ enable = true;
+ virtualMailboxes = a.notmuch.neomutt.virtualMailboxes;
+ };
+ };
+
+ imap = optionalAttrs (a.imap.host != null) {
+ host = a.imap.host;
+ port = a.imap.port;
+ tls = {
+ enable = a.imap.tls.enable;
+ useStartTls = a.imap.tls.useStartTls;
+ };
+ };
+
+ smtp = optionalAttrs (a.smtp.host != null) {
+ host = a.smtp.host;
+ port = a.smtp.port;
+ tls = {
+ enable = a.smtp.tls.enable;
+ useStartTls = a.smtp.tls.useStartTls;
+ };
+ };
+
+ gpg = optionalAttrs (a.gpg.key != null) {
+ key = a.gpg.key;
+ signByDefault = a.gpg.signByDefault;
+ encryptByDefault = a.gpg.encryptByDefault;
+ };
+
+ signature = optionalAttrs (a.signature.text != null || a.signature.command != null) {
+ text = a.signature.text;
+ command = a.signature.command;
+ showSignature = a.signature.showSignature;
+ };
+ }
+ ) (filterAttrs (n: a: a.enable) config.communication.neomutt.accounts);
+ };
+}
diff --git a/modules/communication/neomutt/account/flavors/gmail.nix b/modules/communication/neomutt/account/flavors/gmail.nix
new file mode 100644
index 0000000..5ccceed
--- /dev/null
+++ b/modules/communication/neomutt/account/flavors/gmail.nix
@@ -0,0 +1,45 @@
+{
+ config,
+ lib,
+ name,
+ ...
+}:
+
+{
+ 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";
+ };
+
+ mbsync = {
+ create = lib.mkDefault "maildir";
+ patterns = lib.mkDefault [
+ "INBOX"
+ "[Gmail]/Drafts"
+ "[Gmail]/Sent Mail"
+ "[Gmail]/Spam"
+ "[Gmail]/Trash"
+ ];
+ extraConfig.channel = {
+ Sync = lib.mkDefault "All";
+ CopyArrivalDate = lib.mkDefault "yes";
+ Expunge = lib.mkDefault "Both";
+ SyncState = lib.mkDefault "*";
+ };
+ };
+
+ neomutt.extraConfig = lib.mkDefault ''
+ mailboxes =Inbox ="[Gmail]/Drafts" ="[Gmail]/Sent Mail" ="[Gmail]/Spam" ="[Gmail]/Trash"
+ '';
+
+ notmuch.neomutt.virtualMailboxes = lib.mkDefault [
+ {
+ name = "All Mail";
+ query = "folder:${name}/Inbox or folder:\"${name}/[Gmail]/Sent Mail\"";
+ }
+ ];
+ };
+}
diff --git a/modules/communication/neomutt/account/options.nix b/modules/communication/neomutt/account/options.nix
new file mode 100644
index 0000000..bb53231
--- /dev/null
+++ b/modules/communication/neomutt/account/options.nix
@@ -0,0 +1,328 @@
+{ lib, ... }:
+
+{
+ options = {
+ enable = lib.mkEnableOption "this email account" // {
+ default = true;
+ };
+
+ realName = lib.mkOption {
+ type = lib.types.str;
+ description = "Name displayed when sending mails.";
+ };
+
+ address = lib.mkOption {
+ type = lib.types.str;
+ description = "Email address of this account.";
+ };
+
+ userName = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Server username. Defaults to address if null.";
+ };
+
+ passwordGopassSecret = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Gopass secret path (e.g. mail/user@gmail.com). Constructs passwordCommand automatically.";
+ };
+
+ primary = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Whether this is the primary account.";
+ };
+
+ flavor = lib.mkOption {
+ type = lib.types.nullOr (
+ lib.types.enum [
+ "gmail.com"
+ "fastmail.com"
+ "outlook.office365.com"
+ "outlook.office365.com-ews"
+ "mailbox.org"
+ "migadu.com"
+ "posteo.de"
+ "runbox.com"
+ "yandex.com"
+ "davmail"
+ "plain"
+ ]
+ );
+ default = null;
+ description = "Email provider flavor for automatic configuration.";
+ };
+
+ folders = {
+ inbox = lib.mkOption {
+ type = lib.types.str;
+ default = "Inbox";
+ description = "Inbox folder.";
+ };
+
+ drafts = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Drafts folder.";
+ };
+
+ sent = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Sent mail folder.";
+ };
+
+ trash = lib.mkOption {
+ type = lib.types.str;
+ default = "Trash";
+ description = "Trash folder.";
+ };
+ };
+
+ neomutt = {
+ mailboxType = lib.mkOption {
+ type = lib.types.enum [
+ "maildir"
+ "imap"
+ ];
+ default = "maildir";
+ description = "Whether this account uses maildir folders or IMAP mailboxes.";
+ };
+
+ extraConfig = lib.mkOption {
+ type = lib.types.lines;
+ default = "";
+ description = "Extra neomutt config lines for this account.";
+ };
+ };
+
+ mbsync = {
+ create = lib.mkOption {
+ type = lib.types.enum [
+ "none"
+ "maildir"
+ "imap"
+ "both"
+ ];
+ default = "none";
+ description = "Automatically create missing mailboxes.";
+ };
+
+ patterns = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ "INBOX" ];
+ description = "Mailbox patterns to synchronize.";
+ };
+
+ flatten = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Flatten hierarchy delimiter.";
+ };
+
+ extraConfig = {
+ account = lib.mkOption {
+ type = lib.types.attrsOf (
+ lib.types.oneOf [
+ lib.types.str
+ lib.types.int
+ lib.types.bool
+ (lib.types.listOf lib.types.str)
+ ]
+ );
+ default = { };
+ description = "Account section extra configuration.";
+ };
+
+ channel = lib.mkOption {
+ type = lib.types.attrsOf (
+ lib.types.oneOf [
+ lib.types.str
+ lib.types.int
+ lib.types.bool
+ (lib.types.listOf lib.types.str)
+ ]
+ );
+ default = { };
+ description = "Channel extra configuration.";
+ };
+
+ local = lib.mkOption {
+ type = lib.types.attrsOf (
+ lib.types.oneOf [
+ lib.types.str
+ lib.types.int
+ lib.types.bool
+ (lib.types.listOf lib.types.str)
+ ]
+ );
+ default = { };
+ description = "Local store extra configuration.";
+ };
+
+ remote = lib.mkOption {
+ type = lib.types.attrsOf (
+ lib.types.oneOf [
+ lib.types.str
+ lib.types.int
+ lib.types.bool
+ (lib.types.listOf lib.types.str)
+ ]
+ );
+ default = { };
+ description = "Remote store extra configuration.";
+ };
+ };
+ };
+
+ notmuch = {
+ neomutt = {
+ virtualMailboxes = lib.mkOption {
+ type = lib.types.listOf (
+ lib.types.submodule {
+ options = {
+ name = lib.mkOption {
+ type = lib.types.str;
+ description = "Display name.";
+ };
+
+ query = lib.mkOption {
+ type = lib.types.str;
+ description = "Notmuch query.";
+ };
+
+ limit = lib.mkOption {
+ type = lib.types.nullOr lib.types.int;
+ default = null;
+ description = "Result limit.";
+ };
+
+ type = lib.mkOption {
+ type = lib.types.nullOr (
+ lib.types.enum [
+ "threads"
+ "messages"
+ ]
+ );
+ default = null;
+ description = "Result type.";
+ };
+ };
+ }
+ );
+ default = [ ];
+ description = "Virtual mailboxes from notmuch queries.";
+ };
+ };
+ };
+
+ imap = {
+ host = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "IMAP server hostname.";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.nullOr lib.types.port;
+ default = null;
+ description = "IMAP server port.";
+ };
+
+ tls = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = "Enable TLS.";
+ };
+
+ useStartTls = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Use STARTTLS.";
+ };
+ };
+ };
+
+ smtp = {
+ host = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "SMTP server hostname.";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.nullOr lib.types.port;
+ default = null;
+ description = "SMTP server port.";
+ };
+
+ tls = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = "Enable TLS.";
+ };
+
+ useStartTls = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Use STARTTLS.";
+ };
+ };
+ };
+
+ gpg = {
+ key = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "GPG key to use.";
+ };
+
+ signByDefault = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Sign messages by default.";
+ };
+
+ encryptByDefault = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Encrypt messages by default.";
+ };
+ };
+
+ signature = {
+ text = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "Signature text.";
+ };
+
+ command = lib.mkOption {
+ type = lib.types.nullOr lib.types.path;
+ default = null;
+ description = "Command that generates a signature.";
+ };
+
+ showSignature = lib.mkOption {
+ type = lib.types.nullOr (
+ lib.types.enum [
+ "append"
+ "attach"
+ "none"
+ ]
+ );
+ default = null;
+ description = "Signature display method.";
+ };
+ };
+
+ aliases = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ ];
+ description = "Alternative email addresses for this account.";
+ };
+ };
+}
diff --git a/modules/communication/neomutt/assertions.nix b/modules/communication/neomutt/assertions.nix
index 22434a4..b85280e 100644
--- a/modules/communication/neomutt/assertions.nix
+++ b/modules/communication/neomutt/assertions.nix
@@ -17,13 +17,6 @@
'';
}
{
- assertion = !config.communication.neomutt.enable || config.communication.email.enable;
- message = ''
- communication.neomutt is enabled but communication.email.enable is not.
- neomutt requires the email module (mbsync + notmuch) to be enabled.
- '';
- }
- {
assertion = !config.communication.neomutt.enable || config.terminal.kitty.enable;
message = ''
communication.neomutt is enabled but terminal.kitty.enable is not.
diff --git a/modules/communication/neomutt/default.nix b/modules/communication/neomutt/default.nix
index be3045d..34147fd 100644
--- a/modules/communication/neomutt/default.nix
+++ b/modules/communication/neomutt/default.nix
@@ -1,431 +1,20 @@
-# neomutt - Terminal email client
-{
- config,
- lib,
- pkgs,
- ...
-}:
-let
- neomuttDesktopItem = pkgs.makeDesktopItem {
- name = "neomutt";
- desktopName = "NeoMutt";
- exec = "${lib.getExe config.programs.kitty.package} -e ${lib.getExe config.programs.neomutt.package}";
- icon = "kitty";
- categories = [
- "Network"
- "Email"
- ];
- comment = "Terminal email client";
- terminal = false;
- type = "Application";
- };
-in
+{ config, lib, ... }:
+
{
imports = [
+ ./account
+ ./sync
+ ./neomutt.nix
./assertions.nix
./theme.nix
];
+
options.communication.neomutt = {
- enable = lib.mkEnableOption "Enables neomutt.";
+ enable = lib.mkEnableOption "Email suite (neomutt + mbsync + notmuch)";
};
config = lib.mkIf config.communication.neomutt.enable {
- xdg.mimeApps.defaultApplications = {
- "x-scheme-handler/mailto" = "neomutt.desktop";
- };
-
- home.packages = [
- pkgs.urlscan
- pkgs.w3m
- neomuttDesktopItem
- ];
-
- xdg.configFile."neomutt/mailcap".text = ''
- text/html; w3m -dump -T text/html '%s'; copiousoutput; nametemplate=%s.html
- text/html; $BROWSER '%s'; nametemplate=%s.html
- image/*; xdg-open '%s' &
- application/pdf; xdg-open '%s'; nametemplate=%s.pdf
- video/*; xdg-open '%s' &
- audio/*; xdg-open '%s' &
- application/*; xdg-open '%s' &
- '';
-
- xdg.configFile."neomutt/flag_legend.txt".text = ''
- NeoMutt Index Flags
- ===================
-
- Message state ($flag_chars) -- unchanged defaults
- N New - unread, arrived since you last checked
- O Old - unread, but seen in a previous session
- D Deleted - marked for deletion (pending expunge)
- d Att-del - has attachments marked for deletion
- ! Flagged - important (toggle with F)
- * Tagged - selected for a bulk operation (toggle with t)
- r Replied - you've replied to this message
- (blank) - read, nothing else notable
-
- Cryptography ($crypt_chars)
-  Verified - signed, and the signature checks out
-  Encrypted - PGP/SMIME encrypted
-  Signed - PGP/SMIME signed (unverified)
-  PGP key - message contains a PGP public key
- (blank) - no cryptography info
-
- Addressing ($to_chars)
-  To you only - sent to you and only you
-  To (with others) - you're in the To: list, among others
-  Cc only - you're only in the Cc: list
-  From you - sent by you
-  Mailing list - sent to a list you're subscribed to
- (blank) - not in To/Cc (e.g. Bcc, unlisted)
-
- Press q to close.
- '';
-
- programs.neomutt = {
- sidebar.enable = true;
- sort = "reverse-threads";
- vimKeys = true;
- unmailboxes = true;
- checkStatsInterval = 20;
-
- extraConfig = ''
- set menu_scroll = 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/html image/png image/jpeg image/gif application/pdf
- '';
-
- binds = [
- # --- sidebar navigation (vim: k=up, j=down) ---
- {
- map = [
- "index"
- "pager"
- ];
- key = "\\CK";
- action = "sidebar-prev";
- }
- {
- map = [
- "index"
- "pager"
- ];
- key = "\\CJ";
- action = "sidebar-next";
- }
- {
- # select the highlighted mailbox directly, no Ctrl+O needed
- map = [
- "index"
- "pager"
- ];
- key = "o";
- action = "sidebar-open";
- }
-
- # --- open / go deeper (vim: l = right/forward) ---
- {
- map = [ "index" ];
- key = "<Return>";
- action = "display-message";
- }
- {
- map = [ "index" ];
- key = "l";
- action = "display-message";
- }
- {
- map = [ "browser" ];
- key = "<Return>";
- action = "select-entry";
- }
- {
- map = [ "browser" ];
- key = "l";
- action = "select-entry";
- }
- {
- map = [ "attach" ];
- key = "<Return>";
- action = "view-attach";
- }
- {
- map = [ "attach" ];
- key = "l";
- action = "view-attach";
- }
- {
- map = [
- "alias"
- "query"
- ];
- key = "<Return>";
- action = "select-entry";
- }
-
- # --- back / exit (vim: h = left/back) ---
- {
- map = [ "pager" ];
- key = "h";
- action = "exit";
- }
- {
- map = [ "browser" ];
- key = "h";
- action = "exit";
- }
- {
- map = [ "attach" ];
- key = "h";
- action = "exit";
- }
- {
- map = [ "index" ];
- key = "h";
- action = "collapse-thread";
- }
-
- # --- pager line scrolling (vim: j/k move by line) ---
- {
- map = [ "pager" ];
- key = "j";
- action = "next-line";
- }
- {
- map = [ "pager" ];
- key = "k";
- action = "previous-line";
- }
- {
- # kept from before: jump straight to the next unread message,
- # extends the h/l "back/forward" metaphor already in use
- map = [ "pager" ];
- key = "l";
- action = "next-unread";
- }
-
- # --- top/bottom of buffer (vim: gg/G -- NOT 0/$, which mean
- # start/end of *line* in vim and would be a false friend here) ---
- {
- map = [ "index" ];
- key = "gg";
- action = "first-entry";
- }
- {
- map = [ "index" ];
- key = "G";
- action = "last-entry";
- }
- {
- map = [ "pager" ];
- key = "gg";
- action = "top";
- }
- {
- map = [ "pager" ];
- key = "G";
- action = "bottom";
- }
- {
- map = [ "browser" ];
- key = "gg";
- action = "first-entry";
- }
- {
- map = [ "browser" ];
- key = "G";
- action = "last-entry";
- }
-
- # --- page scrolling (vim: Ctrl-f/b full page, Ctrl-d/u half page) ---
- {
- map = [ "pager" ];
- key = "\\Cf";
- action = "next-page";
- }
- {
- map = [ "pager" ];
- key = "\\Cb";
- action = "previous-page";
- }
- {
- map = [ "pager" ];
- key = "\\Cd";
- action = "half-down";
- }
- {
- map = [ "pager" ];
- key = "\\Cu";
- action = "half-up";
- }
- {
- # index has no half-page concept, so Ctrl-d/u fall back to
- # the same full-page scroll as Ctrl-f/b
- map = [ "index" ];
- key = "\\Cf";
- action = "next-page";
- }
- {
- map = [ "index" ];
- key = "\\Cb";
- action = "previous-page";
- }
- {
- map = [ "index" ];
- key = "\\Cd";
- action = "half-down";
- }
- {
- map = [ "index" ];
- key = "\\Cu";
- action = "half-up";
- }
-
- # --- search (vim: / forward, ? backward, n/N repeat) ---
- {
- map = [
- "index"
- "pager"
- ];
- key = "/";
- action = "search";
- }
- {
- map = [
- "index"
- "pager"
- ];
- key = "?";
- action = "search-reverse";
- }
- {
- map = [
- "index"
- "pager"
- ];
- key = "n";
- action = "search-next";
- }
- {
- map = [
- "index"
- "pager"
- ];
- key = "N";
- action = "search-opposite";
- }
-
- {
- map = [
- "index"
- "pager"
- ];
- key = "u";
- action = "undelete-message";
- }
-
- # --- copy (vim: yy yank -- non-destructive, unlike save-message
- # which moves/deletes the original) ---
- {
- map = [ "index" ];
- key = "y";
- action = "noop";
- }
- {
- map = [ "index" ];
- key = "yy";
- action = "copy-message";
- }
-
- # --- tagging (vim: v/V visual select single/whole block) ---
- # NOTE: this overrides neomutt's default "v" (view-attachments).
- # Attachments are still reachable from the pager/index via the
- # mailcap auto-view or the attach menu, but if you want plain
- # "v" back for view-attachments, just delete these two binds.
- {
- map = [ "index" ];
- key = "v";
- action = "tag-entry";
- }
- {
- map = [ "index" ];
- key = "V";
- action = "tag-thread";
- }
-
- # --- mailbox switching (vim: gt = next tab) ---
- {
- map = [ "index" ];
- key = "gt";
- action = "next-unread-mailbox";
- }
-
- # --- filter/limit (vim: gf to 'find' or 'filter') ---
- {
- map = [ "index" ];
- key = "gf";
- action = "limit";
- }
-
- # --- filter/limit (vim: gf to 'find' or 'filter') ---
- {
- map = [ "index" ];
- key = "gF";
- action = "limit";
- }
-
- # --- help message to show functions and keybindings ---
- {
- map = [
- "index"
- "pager"
- "browser"
- "attach"
- ];
- key = "g?";
- action = "help";
- }
- ];
-
- macros = [
- {
- map = [
- "index"
- "pager"
- ];
- key = "O";
- action = "<sync-mailbox><shell-escape>mbsync -a<enter><sync-mailbox>";
- }
- {
- # gx: extract links from the current message and open one, vim-netrw style
- map = [
- "index"
- "pager"
- ];
- key = "gx";
- action = "<pipe-message>urlscan<Enter>";
- }
- {
- map = [
- "index"
- "pager"
- ];
- key = "gl";
- action = "<shell-escape>less ~/.config/neomutt/flag_legend.txt<enter>";
- }
- {
- # ZZ: save and quit, vim-style (sync mailbox, then quit)
- map = [ "index" ];
- key = "ZZ";
- action = "<sync-mailbox><quit>";
- }
- ];
- };
+ accounts.email.maildirBasePath = "${config.home.homeDirectory}/.local/share/mail";
+ home.sessionVariables.MAILDIR = config.accounts.email.maildirBasePath;
};
}
diff --git a/modules/communication/neomutt/neomutt.nix b/modules/communication/neomutt/neomutt.nix
new file mode 100644
index 0000000..84afdf7
--- /dev/null
+++ b/modules/communication/neomutt/neomutt.nix
@@ -0,0 +1,423 @@
+# neomutt - Terminal email client
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+let
+ neomuttDesktopItem = pkgs.makeDesktopItem {
+ name = "neomutt";
+ desktopName = "NeoMutt";
+ exec = "${lib.getExe config.programs.kitty.package} -e ${lib.getExe config.programs.neomutt.package}";
+ icon = "kitty";
+ categories = [
+ "Network"
+ "Email"
+ ];
+ comment = "Terminal email client";
+ terminal = false;
+ type = "Application";
+ };
+in
+{
+ config = lib.mkIf config.communication.neomutt.enable {
+ xdg.mimeApps.defaultApplications = {
+ "x-scheme-handler/mailto" = "neomutt.desktop";
+ };
+
+ home.packages = [
+ pkgs.urlscan
+ pkgs.w3m
+ neomuttDesktopItem
+ ];
+
+ xdg.configFile."neomutt/mailcap".text = ''
+ text/html; w3m -dump -T text/html '%s'; copiousoutput; nametemplate=%s.html
+ text/html; $BROWSER '%s'; nametemplate=%s.html
+ image/*; xdg-open '%s' &
+ application/pdf; xdg-open '%s'; nametemplate=%s.pdf
+ video/*; xdg-open '%s' &
+ audio/*; xdg-open '%s' &
+ application/*; xdg-open '%s' &
+ '';
+
+ xdg.configFile."neomutt/flag_legend.txt".text = ''
+ NeoMutt Index Flags
+ ===================
+
+ Message state ($flag_chars) -- unchanged defaults
+ N New - unread, arrived since you last checked
+ O Old - unread, but seen in a previous session
+ D Deleted - marked for deletion (pending expunge)
+ d Att-del - has attachments marked for deletion
+ ! Flagged - important (toggle with F)
+ * Tagged - selected for a bulk operation (toggle with t)
+ r Replied - you've replied to this message
+ (blank) - read, nothing else notable
+
+ Cryptography ($crypt_chars)
+  Verified - signed, and the signature checks out
+  Encrypted - PGP/SMIME encrypted
+  Signed - PGP/SMIME signed (unverified)
+  PGP key - message contains a PGP public key
+ (blank) - no cryptography info
+
+ Addressing ($to_chars)
+  To you only - sent to you and only you
+  To (with others) - you're in the To: list, among others
+  Cc only - you're only in the Cc: list
+  From you - sent by you
+  Mailing list - sent to a list you're subscribed to
+ (blank) - not in To/Cc (e.g. Bcc, unlisted)
+
+ Press q to close.
+ '';
+
+ programs.neomutt = {
+ sidebar.enable = true;
+ sort = "reverse-threads";
+ vimKeys = true;
+ unmailboxes = true;
+ checkStatsInterval = 20;
+
+ extraConfig = ''
+ set menu_scroll = 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/html image/png image/jpeg image/gif application/pdf
+ '';
+
+ binds = [
+ # --- sidebar navigation (vim: k=up, j=down) ---
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "\\CK";
+ action = "sidebar-prev";
+ }
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "\\CJ";
+ action = "sidebar-next";
+ }
+ {
+ # select the highlighted mailbox directly, no Ctrl+O needed
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "o";
+ action = "sidebar-open";
+ }
+
+ # --- open / go deeper (vim: l = right/forward) ---
+ {
+ map = [ "index" ];
+ key = "<Return>";
+ action = "display-message";
+ }
+ {
+ map = [ "index" ];
+ key = "l";
+ action = "display-message";
+ }
+ {
+ map = [ "browser" ];
+ key = "<Return>";
+ action = "select-entry";
+ }
+ {
+ map = [ "browser" ];
+ key = "l";
+ action = "select-entry";
+ }
+ {
+ map = [ "attach" ];
+ key = "<Return>";
+ action = "view-attach";
+ }
+ {
+ map = [ "attach" ];
+ key = "l";
+ action = "view-attach";
+ }
+ {
+ map = [
+ "alias"
+ "query"
+ ];
+ key = "<Return>";
+ action = "select-entry";
+ }
+
+ # --- back / exit (vim: h = left/back) ---
+ {
+ map = [ "pager" ];
+ key = "h";
+ action = "exit";
+ }
+ {
+ map = [ "browser" ];
+ key = "h";
+ action = "exit";
+ }
+ {
+ map = [ "attach" ];
+ key = "h";
+ action = "exit";
+ }
+ {
+ map = [ "index" ];
+ key = "h";
+ action = "collapse-thread";
+ }
+
+ # --- pager line scrolling (vim: j/k move by line) ---
+ {
+ map = [ "pager" ];
+ key = "j";
+ action = "next-line";
+ }
+ {
+ map = [ "pager" ];
+ key = "k";
+ action = "previous-line";
+ }
+ {
+ # kept from before: jump straight to the next unread message,
+ # extends the h/l "back/forward" metaphor already in use
+ map = [ "pager" ];
+ key = "l";
+ action = "next-unread";
+ }
+
+ # --- top/bottom of buffer (vim: gg/G -- NOT 0/$, which mean
+ # start/end of *line* in vim and would be a false friend here) ---
+ {
+ map = [ "index" ];
+ key = "gg";
+ action = "first-entry";
+ }
+ {
+ map = [ "index" ];
+ key = "G";
+ action = "last-entry";
+ }
+ {
+ map = [ "pager" ];
+ key = "gg";
+ action = "top";
+ }
+ {
+ map = [ "pager" ];
+ key = "G";
+ action = "bottom";
+ }
+ {
+ map = [ "browser" ];
+ key = "gg";
+ action = "first-entry";
+ }
+ {
+ map = [ "browser" ];
+ key = "G";
+ action = "last-entry";
+ }
+
+ # --- page scrolling (vim: Ctrl-f/b full page, Ctrl-d/u half page) ---
+ {
+ map = [ "pager" ];
+ key = "\\Cf";
+ action = "next-page";
+ }
+ {
+ map = [ "pager" ];
+ key = "\\Cb";
+ action = "previous-page";
+ }
+ {
+ map = [ "pager" ];
+ key = "\\Cd";
+ action = "half-down";
+ }
+ {
+ map = [ "pager" ];
+ key = "\\Cu";
+ action = "half-up";
+ }
+ {
+ # index has no half-page concept, so Ctrl-d/u fall back to
+ # the same full-page scroll as Ctrl-f/b
+ map = [ "index" ];
+ key = "\\Cf";
+ action = "next-page";
+ }
+ {
+ map = [ "index" ];
+ key = "\\Cb";
+ action = "previous-page";
+ }
+ {
+ map = [ "index" ];
+ key = "\\Cd";
+ action = "half-down";
+ }
+ {
+ map = [ "index" ];
+ key = "\\Cu";
+ action = "half-up";
+ }
+
+ # --- search (vim: / forward, ? backward, n/N repeat) ---
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "/";
+ action = "search";
+ }
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "?";
+ action = "search-reverse";
+ }
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "n";
+ action = "search-next";
+ }
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "N";
+ action = "search-opposite";
+ }
+
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "u";
+ action = "undelete-message";
+ }
+
+ # --- copy (vim: yy yank -- non-destructive, unlike save-message
+ # which moves/deletes the original) ---
+ {
+ map = [ "index" ];
+ key = "y";
+ action = "noop";
+ }
+ {
+ map = [ "index" ];
+ key = "yy";
+ action = "copy-message";
+ }
+
+ # --- tagging (vim: v/V visual select single/whole block) ---
+ # NOTE: this overrides neomutt's default "v" (view-attachments).
+ # Attachments are still reachable from the pager/index via the
+ # mailcap auto-view or the attach menu, but if you want plain
+ # "v" back for view-attachments, just delete these two binds.
+ {
+ map = [ "index" ];
+ key = "v";
+ action = "tag-entry";
+ }
+ {
+ map = [ "index" ];
+ key = "V";
+ action = "tag-thread";
+ }
+
+ # --- mailbox switching (vim: gt = next tab) ---
+ {
+ map = [ "index" ];
+ key = "gt";
+ action = "next-unread-mailbox";
+ }
+
+ # --- filter/limit (vim: gf to 'find' or 'filter') ---
+ {
+ map = [ "index" ];
+ key = "gf";
+ action = "limit";
+ }
+
+ # --- filter/limit (vim: gf to 'find' or 'filter') ---
+ {
+ map = [ "index" ];
+ key = "gF";
+ action = "limit";
+ }
+
+ # --- help message to show functions and keybindings ---
+ {
+ map = [
+ "index"
+ "pager"
+ "browser"
+ "attach"
+ ];
+ key = "g?";
+ action = "help";
+ }
+ ];
+
+ macros = [
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "O";
+ action = "<sync-mailbox><shell-escape>mbsync -a<enter><sync-mailbox>";
+ }
+ {
+ # gx: extract links from the current message and open one, vim-netrw style
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "gx";
+ action = "<pipe-message>urlscan<Enter>";
+ }
+ {
+ map = [
+ "index"
+ "pager"
+ ];
+ key = "gl";
+ action = "<shell-escape>less ~/.config/neomutt/flag_legend.txt<enter>";
+ }
+ {
+ # ZZ: save and quit, vim-style (sync mailbox, then quit)
+ map = [ "index" ];
+ key = "ZZ";
+ action = "<sync-mailbox><quit>";
+ }
+ ];
+ };
+ };
+}
diff --git a/modules/communication/neomutt/sync/default.nix b/modules/communication/neomutt/sync/default.nix
new file mode 100644
index 0000000..3fd9362
--- /dev/null
+++ b/modules/communication/neomutt/sync/default.nix
@@ -0,0 +1,8 @@
+{ ... }:
+
+{
+ imports = [
+ ./mbsync.nix
+ ./notmuch.nix
+ ];
+}
diff --git a/modules/communication/neomutt/sync/mbsync.nix b/modules/communication/neomutt/sync/mbsync.nix
new file mode 100644
index 0000000..6d5ad86
--- /dev/null
+++ b/modules/communication/neomutt/sync/mbsync.nix
@@ -0,0 +1,9 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = mkIf config.communication.neomutt.enable {
+ programs.mbsync.enable = true;
+ };
+}
diff --git a/modules/communication/neomutt/sync/notmuch.nix b/modules/communication/neomutt/sync/notmuch.nix
new file mode 100644
index 0000000..9679bf4
--- /dev/null
+++ b/modules/communication/neomutt/sync/notmuch.nix
@@ -0,0 +1,9 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = mkIf config.communication.neomutt.enable {
+ programs.notmuch.enable = true;
+ };
+}
diff --git a/modules/communication/neomutt/theme.nix b/modules/communication/neomutt/theme.nix
index 1d62c55..583b03f 100644
--- a/modules/communication/neomutt/theme.nix
+++ b/modules/communication/neomutt/theme.nix
@@ -58,7 +58,7 @@
color index_tags cyan default # 󰓼 base0C (Transformed tags, %g %J)
# --- Index Message States ---------------------------------------------
- # By targeting *only* `index_subject`, we preserve your multi-color
+ # 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)
@@ -117,7 +117,7 @@
color body brightblack default "^gpg: " #
# --- Sidebar (was completely unset despite sidebar.enable = true) -----
- set sidebar_format = " %D%* %?N? %N?%?F? %F? "
+ set sidebar_format = " %D%* %?N? %N%?F? %F? "
color sidebar_background default default # transparent, blends with terminal bg
color sidebar_divider white default # base03 (matches old tree color)
color sidebar_ordinary white default # base05 (normal mailbox entries)
diff --git a/modules/private/email.example.nix b/modules/private/email.example.nix
index 1c863cc..adf71c3 100644
--- a/modules/private/email.example.nix
+++ b/modules/private/email.example.nix
@@ -1,56 +1,13 @@
-{ config, lib, ... }:
+{ ... }:
{
- config.communication.email.accounts = {
+ config.communication.neomutt.accounts = {
"example" = {
realName = "Example User";
address = "user@gmail.com";
- userName = "user@gmail.com";
- passwordCommand = "${lib.getExe config.security.gopass.package} -o mail/user@gmail.com";
+ passwordGopassSecret = "mail/user@gmail.com";
flavor = "gmail.com";
primary = true;
- neomutt = {
- enable = true;
- mailboxType = "maildir";
- extraConfig = ''
- mailboxes =Inbox ="[Gmail]/Drafts" ="[Gmail]/Sent Mail" ="[Gmail]/Spam" ="[Gmail]/Trash"
- '';
- };
-
- folders = {
- inbox = "Inbox";
- drafts = "[Gmail]/Drafts";
- sent = "[Gmail]/Sent Mail";
- trash = "[Gmail]/Trash";
- };
-
- mbsync = {
- enable = true;
- create = "maildir";
- patterns = [
- "INBOX"
- "[Gmail]/Drafts"
- "[Gmail]/Sent Mail"
- "[Gmail]/Spam"
- "[Gmail]/Trash"
- ];
- extraConfig.channel = {
- SyncState = "*";
- Sync = "All";
- CopyArrivalDate = "yes";
- Expunge = "Both";
- };
- };
-
- notmuch = {
- enable = true;
- neomutt.virtualMailboxes = [
- {
- name = "All Mail";
- query = "folder:arpitchakladar/Inbox or folder:\"arpitchakladar/[Gmail]/Sent Mail\"";
- }
- ];
- };
};
};
}
diff --git a/users/arpit.nix b/users/arpit.nix
index d0414fc..7c1366c 100644
--- a/users/arpit.nix
+++ b/users/arpit.nix
@@ -20,7 +20,6 @@
gaming.steam.enable = false;
# Communication
- communication.email.enable = true;
communication.neomutt.enable = true;
# Development