{ lib, config, pkgs, inputs, ... }: with lib; let cfg = config.modules.email; secretsDirectory = "${(builtins.toString inputs.nix-private)}/secrets"; in { options.modules.email = { enable = mkEnableOption "Enable email accounts configuration"; accountConfigs = mkOption { type = types.attrsOf types.attrs; default = false; description = "List of account variables used to create accounts"; }; primary = mkOption { type = types.str; default = false; description = "name of primary account"; }; }; config = mkIf cfg.enable { sops = let secretList = lib.mapAttrs' ( name: value: nameValuePair (value.secret.key) { sopsFile = "${secretsDirectory}/${value.secret.file}"; } ) cfg.accountConfigs; in { secrets = secretList; }; accounts.email = let defaultSetting = { mbsync = { enable = true; create = "both"; expunge = "both"; remove = "both"; patterns = [ "*" # Trash is a pain in the ass to manage with notmuch "!Trash" ]; }; msmtp = { enable = true; }; notmuch = { enable = true; }; neomutt = { enable = true; showDefaultMailbox = false; }; }; accountsList = lib.mapAttrs (name: value: lib.recursiveUpdate defaultSetting value.config) cfg.accountConfigs; in { maildirBasePath = "mail"; accounts = lib.recursiveUpdate accountsList {"${cfg.primary}".primary = true;}; }; programs.afew = let mailMoverRules = lib.mergeAttrsList ( lib.attrsets.mapAttrsToList ( n: v: if lib.hasAttrByPath ["afew" "mailMover"] v then v.afew.mailMover else {} ) cfg.accountConfigs ); mailFilterRules = lib.flatten ( lib.attrsets.mapAttrsToList ( n: v: if lib.hasAttrByPath ["afew" "filters"] v then v.afew.filters else [] ) cfg.accountConfigs ); in { enable = true; extraConfig = '' [FolderNameFilter] folder_explicit_list = archives folder_transforms = archives:archived maildir_separator = / [MailMover] folders = ${lib.concatStringsSep " " (lib.unique (lib.mapAttrsToList (n: v: "${n}") mailMoverRules))} rename = True ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n} = ${toString v}") mailMoverRules)} [SpamFilter] [KillThreadsFilter] [ArchiveSentMailsFilter] sent_tag = sent ${lib.concatImapStrings (p: e: "\n\n[Filter.${toString p}]\n ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: "${n} = ${v}") e)}") mailFilterRules} [InboxFilter] ''; }; programs.neomutt = let accountMacros = lib.imap1 ( i: elem: elem // { map = ["index" "pager"]; key = ""; } ) ( lib.attrsets.mapAttrsToList ( n: c: {"action" = "source ~/.config/neomutt/${n}Unread:${c.config.address}";} ) cfg.accountConfigs ); in { enable = true; unmailboxes = true; changeFolderWhenSourcingAccount = true; sourcePrimaryAccount = false; editor = "nvim +/^$/ +':nohl'"; settings = { sleep_time = "1"; mbox_type = "Maildir"; header_color_partial = "yes"; duplicate_threads = "yes"; index_format = "'%4C| %24.24?GS?%F %GS &%F? %?GR?%GR &%?GU?%GU & ??%?GA?%GA & ?%?GE?%GE & ?%?M?󰘕 %s&%s? %* %?g?  %g? %<[y?%<[m?%<[d?%9[%H:%M ]&%9[%a %d ]>&%9[%b %d ]>&%9[%m/%y ]>'"; sort_aux = "last-date-sent"; mail_check = "120"; hidden_tags = "inbox,unread,draft,flagged,passed,replied,signed,encrypted,attachment,sent"; markers = "no"; wrap = "90"; smart_wrap = "yes"; reflow_text = "yes"; reflow_wrap = "90"; text_flowed = "yes"; search_context = "3"; pager_context = "5"; pager_index_lines = "10"; rfc2047_parameters = "yes"; edit_headers = "yes"; send_charset = "utf-8"; envelope_from = "yes"; use_envelope_from = "no"; my_status = "' %o/%m  | %l 󰉉 | %f %* Sort: %s-%S Pos: %P '"; my_pager = "'  %F |  %s %* Pos: %P '"; compose_format = "' COMPOSE  %a | 󰉉 %l'"; query_command = "'${pkgs.khard}/bin/khard email --parsable --search-in-source-files %s'"; virtual_spoolfile = "yes"; mail_check_stats = "yes"; mh_purge = "yes"; mailcap_path = "${config.xdg.configHome}/neomutt/mailcap"; }; extraConfig = '' ${(builtins.readFile ./files/theme.muttrc)} set reverse_name charset-hook ^iso-8859-1$ cp1252 ignore * unignore from date subject to cc bcc tags user-agent x-mailer # Attachment auto_view text/x-vcard text/html text/enriched text/calendar alternative_order text/html text/enriched text/plain text/* tag-transforms "attachment" "󰁦" \ "encrypted" "󱧈" \ "signed" "󱅞" \ "unread" "" \ "replied" "" tag-formats "attachment" "GA" \ "encrypted" "GE" \ "signed" "GS" \ "unread" "GU" \ "replied" "GR" ${lib.concatStrings (lib.attrsets.mapAttrsToList (_: v: if lib.hasAttrByPath ["neomuttHooks"] v then v.neomuttHooks else "") cfg.accountConfigs)} # manually source first account instead of use home-manager parameter because # of $my_pager expansion does not work as this variable is not already set source ${config.xdg.configHome}/neomutt/${cfg.primary} ''; binds = [ { map = ["attach" "browser" "index" "pager"]; key = "g"; action = "noop"; } { map = ["attach" "browser" "index" "pager"]; key = "G"; action = "noop"; } { map = ["index"]; key = "q"; action = "noop"; } { map = ["pager"]; key = "Q"; action = "noop"; } { map = ["attach" "browser" "index"]; key = "gg"; action = "first-entry"; } { map = ["attach" "browser" "index"]; key = "G"; action = "last-entry"; } { map = ["pager"]; key = "gg"; action = "top"; } { map = ["pager"]; key = "G"; action = "bottom"; } { map = ["pager"]; key = "k"; action = "previous-line"; } { map = ["pager"]; key = "j"; action = "next-line"; } # Scrolling { map = ["attach" "browser" "pager" "index"]; key = "\\CF"; action = "next-page"; } { map = ["attach" "browser" "pager" "index"]; key = "\\CB"; action = "previous-page"; } { map = ["attach" "browser" "pager" "index"]; key = "\\Cu"; action = "half-up"; } { map = ["attach" "browser" "pager" "index"]; key = "\\Cd"; action = "half-down"; } { map = ["browser" "pager"]; key = "\\Ce"; action = "next-line"; } { map = ["browser" "pager"]; key = "\\Cy"; action = "previous-line"; } { map = ["index"]; key = "\\Ce"; action = "next-line"; } { map = ["index"]; key = "\\Cy"; action = "previous-line"; } # Reply { map = ["pager" "index"]; key = "R"; action = "group-reply"; } # sidebar { map = ["index" "pager"]; key = ""; action = "sidebar-toggle-visible"; } { map = ["index" "pager"]; key = "{"; action = "sidebar-prev"; } { map = ["index" "pager"]; key = "}"; action = "sidebar-next"; } { map = ["index" "pager"]; key = "|"; action = "sidebar-open"; } # open virtual folder { map = ["index" "pager"]; key = "X"; action = "noop"; } { map = ["index" "pager"]; key = "X"; action = "change-vfolder"; } # read entire thread of the current message { map = ["index" "pager"]; key = "+"; action = "entire-thread"; } # generate virtual folder from query { map = ["index" "pager"]; key = "\\eX"; action = "vfolder-from-query"; } # generate virtual folder from query with time window { map = ["index" "pager"]; key = "\\CD"; action = "modify-tags"; } # Editor { map = ["editor"]; key = ""; action = "complete-query"; } { map = ["editor"]; key = "^T"; action = "complete"; } ]; macros = [ { map = ["pager"]; key = "\\CB"; action = "${pkgs.urlscan}/bin/urlscan -d -c --color true"; } ] ++ accountMacros; }; programs.mbsync = { enable = true; }; services.mbsync = { enable = true; postExec = "${pkgs.notmuch}/bin/notmuch new"; }; programs.notmuch = { enable = true; new.tags = ["new"]; hooks.postNew = '' ${pkgs.afew}/bin/afew --tag --new ${pkgs.afew}/bin/afew --move --all ''; }; programs.msmtp = { enable = true; }; home.file."${config.xdg.configHome}/urlscan/config.json".source = ./files/urlscan.config.json; xdg.configFile."neomutt/mailcap".text = '' text/html; ${pkgs.xdg-utils}/bin/xdg-open %s; nametemplate=%html text/html; ${pkgs.w3m}/bin/w3m -I %{charset} -cols 90 -T text/html %s; copiousoutput application/pdf; ${pkgs.xdg-utils}/bin/xdg-open %s & image/*; ${pkgs.xdg-utils}/bin/xdg-open %s & ''; }; }