id = "mail-deduplicate"
name = "Mail Deduplicate"
homepage = "https://kdeldycke.github.io/mail-deduplicate/"
description = "Remove duplicate emails from your mailboxes."
executables = ["mdedup"]
[dotfiles]
paths = ["[appData]/mdedup/", "[configHome]/mdedup/"]
secret = []
[commands]
format = 1
label = "Deduplicate mailboxes"
description = "Find duplicate mails across one or more mailboxes, pick which copy to keep with a strategy, then act on the result. The default action only copies the selection to a new box. Hash tuning, thresholds and the regexp-based strategies are left out."
quality = "ai-draft"
parser = { end_of_options = "--", interleave = "permute", long_equals = true, short_attached = true, short_bundles = false }
[[commands.source]]
id = "help"
kind = "help"
url = "https://kdeldycke.github.io/mail-deduplicate/cli.html"
revision = "2026-10-27"
evidence = """
Usage: mdedup [OPTIONS] MAIL_SOURCE_1 MAIL_SOURCE_2 ...
-i, --input-format [maildir|mbox|mh|babyl|mmdf|eml]
-b, --hash-body [skip|raw|normalized]
--cache / --no-cache
-s, --strategy [select-older|select-oldest|select-newer|select-newest|discard-newest|discard-newer|discard-oldest|discard-older|select-smaller|select-smallest|select-bigger|select-biggest|discard-biggest|discard-bigger|discard-smallest|discard-smaller|select-matching-path|select-non-matching-path|discard-non-matching-path|discard-matching-path|select-one|select-all-but-one|discard-all-but-one|discard-one]
-a, --action [copy-selected|copy-discarded|move-selected|move-discarded|delete-selected|delete-discarded|hardlink-discarded]
-E, --export MAIL_BOX_PATH
-e, --export-format [maildir|mbox|mh|babyl|mmdf|eml]
--export-append
-n, --dry-run
-H, --hash-only
"""
[[commands.field]]
id = "sources"
label = "Mail sources"
description = "Mailboxes (maildir, mbox, ...) or mail files to deduplicate."
type = "path"
path = { kind = "any", role = "input" }
position = 0
multiple = true
min = 1
sources = ["help"]
[[commands.field]]
id = "strategy"
label = "Selection strategy"
description = "Which copy of each duplicate set is selected. Without one, nothing is selected and no action is taken."
type = "string"
option = ["--strategy", "-s"]
suggestions = [{ value = "select-oldest", label = "Select oldest" }, { value = "select-newest", label = "Select newest" }, { value = "select-biggest", label = "Select biggest" }, { value = "select-smallest", label = "Select smallest" }, { value = "select-one", label = "Select one copy" }, { value = "select-all-but-one", label = "Select all but one" }]
sources = ["help"]
[[commands.field]]
id = "action"
label = "Action"
description = "Default copy-selected only reads the sources and writes a new box. The delete-* and move-* actions change the sources; rehearse with Dry run."
type = "enum"
option = ["--action", "-a"]
choices = [{ value = "copy-selected", label = "Copy selected" }, { value = "copy-discarded", label = "Copy discarded" }, { value = "move-selected", label = "Move selected" }, { value = "move-discarded", label = "Move discarded" }, { value = "delete-selected", label = "Delete selected" }, { value = "delete-discarded", label = "Delete discarded" }, { value = "hardlink-discarded", label = "Hardlink discarded" }]
sources = ["help"]
[[commands.field]]
id = "export"
label = "Destination mailbox"
description = "Required by the copy and move actions."
type = "path"
option = ["--export", "-E"]
path = { kind = "any", role = "output", must_exist = false }
effect = "overwrite"
sources = ["help"]
[[commands.field]]
id = "export_format"
label = "Destination format"
type = "enum"
option = ["--export-format", "-e"]
choices = [{ value = "maildir", label = "Maildir" }, { value = "mbox", label = "mbox" }, { value = "mh", label = "MH" }, { value = "babyl", label = "Babyl" }, { value = "mmdf", label = "MMDF" }, { value = "eml", label = "eml files" }]
observed_default = "mbox"
sources = ["help"]
[[commands.field]]
id = "export_append"
label = "Append to existing destination"
type = "boolean"
option = ["--export-append"]
sources = ["help"]
[[commands.field]]
id = "input_format"
label = "Force input format"
description = "Leave unset to auto-detect maildir, mbox and eml."
type = "enum"
option = ["--input-format", "-i"]
choices = [{ value = "maildir", label = "Maildir" }, { value = "mbox", label = "mbox" }, { value = "mh", label = "MH" }, { value = "babyl", label = "Babyl" }, { value = "mmdf", label = "MMDF" }, { value = "eml", label = "eml files" }]
sources = ["help"]
[[commands.field]]
id = "hash_body"
label = "Hash mail bodies"
type = "enum"
option = ["--hash-body", "-b"]
choices = [{ value = "skip", label = "Skip (headers only)" }, { value = "raw", label = "Raw" }, { value = "normalized", label = "Normalized" }]
observed_default = "skip"
sources = ["help"]
[[commands.field]]
id = "cache"
label = "Reuse hash cache"
type = "boolean"
option = ["--cache"]
false_option = "--no-cache"
sources = ["help"]
[[commands.field]]
id = "dry_run"
label = "Dry run"
description = "Report what would be done without doing it."
type = "boolean"
option = ["--dry-run", "-n"]
sources = ["help"]
[[commands.task]]
id = "merge"
label = "Merge into a new mailbox"
fields = ["sources", "strategy", "export", "dry_run"]
required = ["sources", "strategy", "export"]
set = { strategy = ["select-one"] }
[[commands.task]]
id = "rehearse_delete"
label = "Rehearse deleting duplicates"
fields = ["sources", "strategy", "action", "dry_run"]
required = ["sources", "strategy", "action", "dry_run"]
set = { strategy = ["select-one"], action = ["delete-discarded"], dry_run = [true] }
[[commands.group]]
id = "basic"
label = "Basic"
fields = ["sources", "strategy", "action", "export", "dry_run"]
[[commands.group]]
id = "advanced"
label = "Advanced"
fields = ["export_format", "export_append", "input_format", "hash_body", "cache"]