Changelog
All notable changes to tmail. Generated from the commit history by git-cliff.
0.2.1 - 2026-10-30
Section titled “0.2.1 - 2026-10-30”Bug fixes
Section titled “Bug fixes”- release: Set the git identity before merging the release back into develop
- build: Use a fixed copyright year in the package metadata
Documentation
Section titled “Documentation”- readme: Bring the README up to date and link the website
0.2.0 - 2026-10-30
Section titled “0.2.0 - 2026-10-30”Features
Section titled “Features”- app: Build the shell completion scripts from the registered commands
- app: Add tmail completion for bash, zsh and fish
- commands: Parse the command line with quotes and escapes
- commands: Add the command registry and runner
- scripting: Add the commands module
- input: Add the command.start action bound to :
- state: Add the UI state file
- watch: Keep the services in one tmail with a lock
- input: Add the reader actions and publish the action names
- tui: Add the mail gateway
- tui: Add the session that runs the services of the interface
- tui: Add the message list view model
- tui: Add the sidebar view model
- tui: Add the reader view model
- tui: Add the status, command line and help view models
- tui: Add the shell view model and the action router
- tui: Poll the folders that the watcher does not cover
- tui: Add the key translator, the theme mapping and the text helpers
- tui: Add the app loop and the terminal host
- tui: Add the layout, sidebar, message list, status line and command line views
- tui: Add the reader and the help overlay
- tui: Start the interface with tmail
- tui: Size the sidebar to its names
- input: Add an optional description to a Lua key binding
- tui: List the keys that can follow a pending sequence
- tui: Show the which-key panel after a pending key
- tui: Draw the which-key panel
- tui: Add flag, move and search to the mail gateway
- tui: Add a search mode to the message list
- tui: Act on messages and search the open folder
- tui: Ask for the send confirmation on the command line
- tui: Write, reply and forward
- tui: Send what is written from the interface
- tui: Message log, jobs, about and cache download
- release: Generate THIRD-PARTY-NOTICES.md and check it in CI
- release: Also generate THIRD-PARTY-NOTICES.txt
- jobs: List jobs the caller runs, report progress and tell about changes
- app: Show the third-party notices in the About window
- tui: :cache.stats
- mail: Open the encrypted mail that Proton sends as a multipart/mixed
- tui: A top bar that scripts fill
- mail: Convert an HTML-only body to Markdown
- tui: Wrap styled text into reader lines
- tui: Draw Markdown as styled reader lines
- tui: Show an HTML-only message as formatted text
- scripting: Sys.date, a clock for Lua
- themes: Put the symbols of the interface in the themes
- themes: Put the bullet, the rule and the ellipsis in the themes
- plugins: Add DLL contract and isolated loader for #126
- plugins: Integrate configured bundles with TUI and watch
- mail: Carry the thread headers of a message in its summary
- mail: Group messages into conversations
- tui: Show the message list as conversations
- plugins: Register native commands and keybindings
- tui: Run plugin commands and show loaded plugins
- man: Add the manual pages tmail(1) and tmail-config(5)
- config: Add the langs folder
- config: Add the language key
- localization: Add the text catalog
- localization: Embed the English texts and apply the language
- localization: Dates follow the language, and a failed script command has a catalog text
- localization: Add the Italian texts and describe the languages
- localization: Translate the conversation messages and describe the language key in the manual
- themes: Give the rows of a conversation their own background
- about: Say that ~tilde sponsors FLUG Firenze
- compose: Look up the public key of a recipient before sending
- gpg: Find the key that signed a message and read its trust
- reader: Look up the key of an unknown-key signature and show its trust
- themes: Ship eight ready-made themes in the repository and the .deb
- chat: Add shared backend contracts and immutable models
- tui: Share conversation list snapshots across mail and chat
- langs: Ship French, German and Spanish and read the languages of the package
- langs: Install the languages in the .deb and the archives
- langs: Translate the signer key and trust texts into French, German and Spanish
- state: Keep the state of the interface in ui.toml
- events: Publish key_imported when a public key is imported
- tui: Show the raw source of a message with message.show_source
- tui: Show the encryption, the signature and the key on their own lines in the reader
- docs: Publish the documentation as a website on Codeberg Pages
- docs: Show the version and a credit line on the website
- tui: Hide and show the sidebar with sidebar.toggle on
e - commands: Ask before :q quits and add :q! to quit without asking
- docs: Use a tilde as the blinking cursor of the home page
- docs: Put the root documents on the website
Bug fixes
Section titled “Bug fixes”- app: Print the completion script before any start-up work
- app: Exit 1 instead of crashing when standard output is closed
- state: Never save a UI state file that cannot be read back
- commands: Apply the minor review points
- tui: Draw the frames with the border color
- tui: Never hold the scheduler lock while posting to the screen
- tui: Start the exit guard before asking the screen to stop
- tui: Mark a message read after it was fetched
- tui: Fix the findings of the final review
- tui: Close the follow-ups of the piece 1 review
- tui: Close the findings of the global review
- tui: Hand the terminal to gpg only when it needs the passphrase
- tui: Keep the which-key panel up while it is read
- tui: Close the findings of the review of the message actions
- tui: Never lose what was written
- tui: Make the cache download stop and count per account
- tui: Close the minor findings of the cache download review
- tui: Keep the cache download off the workers and coalesce the status refresh
- tui: Read the cache figures off the interface thread and pin that no cache is created
- mail: Repair a Proton message only when nothing else is in it
- tui: Bound the top bar and make a light theme’s bar light
- tui: Harden the HTML mail reader after review
- scripting: Refuse an os.date style format in sys.date
- plugins: Resolve private System dependencies and Release fixtures
- tui: Harden the conversation view after review
- man: Address the review of the manual pages
- localization: Move the last texts, cache the culture and tighten the Italian
- compose: Accept an offered key without making other keys valid
- reader: Accept a signature made by a subkey and tighten the key lookup
- plugins: Rename listing command to plugins.show
- themes: Tighten the contrast of the shipped themes and name both folders
- langs: Correct the security wording and the layout of the translations
- state: Write a replacement character escaped so the state stays readable
- hosting: Keep startup independent of the caller context
- storage: Initialize directories and caches explicitly
- hosting: Register outer cancellation during signal startup
- tui: Cancel and release incomplete startup
- tui: Keep the source window to the asked message and bound joined emoji
- plugins: Reject script targets in native keybindings
- tui: Never say a key can be relied on under an untrusted signature
- docs: Build the released sources, serialize the site publishing and check the page list
- plugins: Share the extracted mail runtime with plugin hosts
- mail: Restore the old-location check and correct the docs the extraction made stale
Refactoring
Section titled “Refactoring”- config: Keep the state folder in DirectoryType
- watch: Share the new-mail step with the interface
- tui: Let ReactiveUI write the property notifications
- tui: Run and list the jobs of the interface on the library scheduler
- Use Humanizer for sizes, counts and cut texts
- plugins: Modernize loader synchronization and collection syntax
- localization: The status line reads its texts from the catalog
- localization: The actions and the compose confirmation read their texts from the catalog
- localization: The reader, the windows and the help read their texts from the catalog
- localization: The commands, the actions and the send messages read their texts from the catalog
- localization: The tmail commands read their texts from the catalog
- gpg: Name a key source in one place
- tui: Activate view models and views explicitly
- tui: Cut the message source at a character and write the sizes for people
- core: Extract shared protocol and plugin contracts
- plugins: Register contributions through shared contracts
- mail: Declare the display text extensions in an extension block
- scripting: Extract Lua runtime from Core
- Move the files to the bucket-first layout
- Rename the namespaces to match the bucket-first folders
- BREAKING Close the findings of the review of the bucket-first layout
- core: Expose shared text helpers for mail services
- mail: Extract mail runtime from Core
Documentation
Section titled “Documentation”- readme: Reword the first line of the AI section
- Clarify AI role in tests and planning
- Document the interface
- Describe the interface as it is now
- readme: Add a screenshot of tmail
- readme: Replace the screenshot with one that has no private mail
- Fix the top_status example, which used a library the sandbox does not open
- Add plugin hosting guide
- Clarify plugin dependency policy
- plugins: Document native commands and keybindings
- plugins: Clarify command binding behavior
- themes: Count the new role, say which roles follow the theme and check the tint contrast
- chat: Document shared backend contracts
- Describe conversation list projection
- readme: Describe the current features and fix the interface limits
- Add a step-by-step tutorial to install and configure tmail
- tutorial: Warn about untrusted imported keys and fix small inaccuracies
- Fix the stale and inaccurate statements found in the review
- Put the mascot in the README logo
- conventions: Permit positional DTO records and resolve ambiguous references
- architecture: Describe the shared SDK boundary and migration
- Describe the bucket-first layout and its namespaces
- Say what the leader binding of the sidebar changes for waiting keys and shadowed bindings
- State the general form of a path and its namespace in the code convention
- mail: Document the library and its NuGet package
0.1.1 - 2026-10-28
Section titled “0.1.1 - 2026-10-28”Bug fixes
Section titled “Bug fixes”- app: Print one-line command summaries in –help
- release: Finish a release when main is already tagged
- Stop two tests from depending on timing
Documentation
Section titled “Documentation”- readme: Say how the code is written
- readme: Say that tests come first and are written with the assistant too
0.1.0 - 2026-10-28
Section titled “0.1.0 - 2026-10-28”Features
Section titled “Features”- config: Load accounts from config.toml
- secrets: Expand env and command placeholders
- mail: Add mail model and MIME mapping
- gpg: Sign, verify, encrypt and decrypt through gpg
- mail: Add IMAP backend and SMTP sender
- compose: Add compose file and reply builder
- compose: Add compose service with GPG
- search: Parse search input and run IMAP search
- scripting: Add Lua hooks and keybindings
- sync: Watch folders for new mail
- app: Wire entrypoint, container and docs
- security: Check the signer against From and sanitise attachment names
- app: Publish tmail as a single-file executable
- scripting: Add ScriptEngineOptions
- scripting: Bind modules from a registry filled by the container
- scripting: Generate definitions.lua and .luarc.json for editors
- scripting: Add the log module and a print prelude
- scripting: Reload the scripts
- jobs: Add the job types and the [jobs] configuration table
- jobs: Queue jobs on a pool of workers
- jobs: Run recurring jobs every interval without overlap
- jobs: Register the scheduler in the container
- scripting: Start and list jobs from Lua
- config: Declare sender identities inside an account
- compose: Write and send as a sender identity
- compose: Reply from the identity a message was addressed to
- config: Name a signature file per account and identity
- compose: Add the signature of the sender identity to the draft
- config: Add the [cache] section
- mail: Expose folder state and incremental listing on the backend
- cache: Add the SQLite mail cache
- cache: Add the caching mail backend
- app: Use the mail cache and add the cache commands
- app: Show and clear the caches of renamed or removed accounts
- input: Parse vim key notation into chords and sequences
- input: Add key contexts and the closed set of named actions
- config: Add the [keys] table with preset, leader and timeout
- input: Add the keymap store and the vim preset
- input: Add the input dispatcher with counts, sequences and timeout resolution
- scripting: Bind keys per context from Lua with named actions
- cache: Add the FTS5 index to the cache schema
- cache: Index sender and subject and search them with FTS5
- cache: Index the text of the messages the cache stores
- cache: Search the cache when the server cannot be reached
- input: Run a binding that is also a prefix when the next key does not continue it
- rules: Add the rule model, matcher and planner
- mail: Create folders and set IMAP keywords on the backend
- rules: Execute a rule plan against the backend
- scripting: Add the rules Lua module
- rules: Apply rules to new mail and to a folder
- app: Add the tmail rules run command
- config: Declare a Maildir account
- maildir: Parse Maildir file names and flags
- maildir: Index Maildir messages with stable UIDs
- maildir: Read folders and messages from a Maildir
- maildir: Set flags, move and append on a Maildir
- maildir: Search and watch a Maildir
- maildir: Use a Maildir backend for Maildir accounts
- events: Add the in-process event hub
- events: Run Lua handlers for hub events and fire the script events
- events: Publish message_sent, send_failed, flags_changed and message_moved
- events: Publish offline and online
- events: Publish account_error, job_failed, signature_checked and decrypt_failed
- app: Add the watch command
- app: Wire tmail watch with signal handling
- mail: Clean message text for the terminal
- mail: Open a message for reading
- hosting: Start and stop services in priority order
- hosting: Give the event hub, script engine, scheduler, watcher and backends a lifecycle
- themes: Add the palette roles and the #RRGGBB color type
- themes: Add the Theme record, the built-in default and AppDirectories.Themes
- themes: Load themes from the themes folder with fallback to the default palette
- themes: Read the active theme from config.toml and resolve it with a safe fallback
- themes: Add the tmail themes list and show commands
- scripts: Add build-binaries.sh for the release binaries
- config: Detect unknown keys and tables in config.toml
- check: Warn about unknown config.toml keys
- ci: Add Forgejo workflows and Debian packages
- release: Semantic release and automatic changelog from the CI
- scripting: Add the gpg module with has_key
- config: Accept “auto” for gpg.encrypt
- compose: Resolve encrypt = “auto” when the editor closes
- gpg: Search and import public keys from key servers
- scripting: Add gpg.search_key and gpg.import_key
- app: Add tmail gpg fetch
- compose: Add an X-Mailer header to outgoing messages
- compose: Send a finished draft without the editor or a confirmation
- send: Add the tmail send command
- mail: Pin the certificate of a self-signed IMAP or SMTP server
- mail: Let a server skip the certificate check, and fix the review findings
- log: Add a log level and debug logs for what a command does
- app: Print the author and the project address with –version
Bug fixes
Section titled “Bug fixes”- secrets: Bound output reads and observe aborted reads
- gpg: Match recipients exactly and report untrusted signatures
- mail: Share one attachment enumeration and skip PGP protocol parts
- mail: Reconnect and retry once when the IMAP connection is stale
- scripting: Cap string.rep and never hang on dispose
- secrets: Expand credentials in one pass and keep them raw at load
- gpg: Keep gpg diagnostics off the status channel
- compose: Send through the composing account and never lose written text
- mail: Lock backend creation per account
- search: Match every free word instead of one phrase
- scripting: Follow a safe init.lua link and report a refused one
- scripting: Make ScriptHost.Dispose atomic
- sync: Keep watching through UIDVALIDITY changes, restarts and disposal
- app: Report file system errors instead of crashing
- scripting: Escape markup in the notification body
- compose: Keep the message file out of the shared temp directory
- config: Validate addresses and refuse commands outside credentials
- mail: Report mail that arrived during a UIDVALIDITY change
- gpg: Keep a signature good only for a certified sender user id
- config: Keep values out of TOML and address error messages
- secrets: Pass ${NAME} into credential commands as data
- mail: Bound and stabilise new-mail reports after a UIDVALIDITY change
- gpg: Report log-file failures as GpgException
- mail: Drop line separators and leading dashes from attachment names
- scripting: Refuse an init.lua symbolic link loop
- config: Never print account credentials from AccountConfig.ToString
- gpg: Assess only a From header with exactly one mailbox
- mail: Never anchor a UIDVALIDITY recovery before the last healthy fetch
- compose: Stop Unicode line separators from smuggling recipients
- compose: Encrypt to the sender’s configured key, not the account address
- security: Harden gpg recipients, gpg.conf use, account names and permissions
- config: Make restricting directory modes best effort
- gpg: Clarify the search prefix error
- scripting: Run a key against the current state and publish signature enums
- jobs: Cancel queued jobs at once and tidy the review findings
- identities: Inherit each GPG field and answer own messages from the same identity
- signatures: Bound the file read, cancel on a signature-only draft and match the block by lines
- mail: Specify unknown-folder and edge behaviour of the new backend members
- sync: Reject a cancelled token before the watcher starts
- tests: Run the tests that start processes one after the other
- cache: Keep raw reads from blocking and from rewriting the message
- cache: Close the review gaps of the caching backend
- app: Read the cache without changing it and explain a locked cache
- cache: Pause the last-access update after it fails
- cache: Trust raws only for listed folders and throttle the watch
- cache: Drop deleted rows below the range and confirm folders with the server
- sync: Take the new-mail baseline from the server
- cache: Remember the server of a cache and find orphan databases
- app: Delete orphan caches only with clear –orphans
- cache: Re-read the folder state after waiting for the folder gate
- input: Parse a Ctrl+minus chord followed by > and tidy KeySequence
- input: Take the script’s owning context from the keymap lookup
- input: Reject a digit leader and bindings that start with a count digit
- input: Normalise Control chords in KeyChord and reject an empty key
- scripting: Report a nil rhs in keys.bind(context, lhs, rhs)
- cache: Keep the index in step with late and changed headers
- input: Make KeyResult.Action null unless the result is an action
- rules: Treat INBOX case-insensitively and refuse blank condition strings
- mail: Survive a concurrent folder creation and refuse names outside the namespace prefix
- app: Warn when tmail rules run answers from the cache, sanitise format characters
- maildir: Keep UIDs through renames and ignore links and special files
- events: Report a second outage, keep the order and bound the queue
- app: Keep each watch message with its outcome and survive failed writes
- mail: Report every reading failure and keep the text safe
- hosting: Always stop every service, stop a hung start, give slow services a budget
- themes: Report non-regular theme files as invalid and document the list exceptions
- themes: Follow the spec when config.toml cannot be loaded and tidy the review findings
- themes: Match theme names with exact case and tidy two messages
- themes: Find hidden theme files and wrap a long test line
- app: Keep printing script errors while the services stop
- scripts: Resolve –output before changing directory, check the version, drop stale checksums
- config: Harden the key auditor and document the misplaced-theme warning
- ci: Draft releases, scoped token, no pre-release in the registry
- maildir: Create the index schema inside one write transaction
- release: Resume a failed release, push atomically and keep the changelog tidy
- gpg: Tell a failing gpg from a missing key
- gpg: Treat expired, revoked and disabled keys as missing
- gpg: Check the encryption capability and limit the disabled rule to addresses
- config: Name the account in the gpg.encrypt error
- compose: Tidy the auto edge cases found in review
- config: Name the gpg.encrypt key like its siblings and order the usings
- gpg: Keep key lookups out of the user keyring and test the hostile server cases
- gpg: Import only the requested keyblock
- gpg: Count raw pub records and tidy two details
- send: Take the GPG flags of the –from identity and report what the review found
- log: Indent exception lines on stderr and tighten the log tests
Performance
Section titled “Performance”- cache: Read the cached UIDs once per sync
Refactoring
Section titled “Refactoring”- scripting: Rename ScriptHost to ScriptEngine
- Use block bodies for every method
- jobs: Move IRecurrence under Interfaces
- Put Dispose last in every disposable type
- app: Share the console text sanitiser and add the watch session
- app: Run tmail watch through the service lifecycle
Documentation
Section titled “Documentation”- readme: Explain how to run the live IMAP and SMTP tests
- readme: Describe credentials, scripting and composing as they work
- mail: Document the UIDVALIDITY gap between watch calls
- mail: Describe the real UIDVALIDITY recovery and carry-over gap
- Correct the gpg stderr note and document credential limitations
- State the check guarantees precisely and the gpg key recipient
- Add the TildeMail logo
- Add icons derived from the logo
- Drop the neomutt reference from the README
- Say which files the single-file publish needs
- Describe the script modules, the definitions and the reload
- Describe the cache commands and the lazy connection accurately
- Rewrite the README for the current state of develop
- Replace the scaffold code convention with the Moongate one adapted to TildeMail
- Document key bindings and the [keys] table
- Correct the key bindings notes on waiting, errors and digits
- Describe the local search
- Document the Lua rules and tmail rules run
- Document INBOX matching, blank strings, header download and the cache fallback
- Describe Maildir accounts
- Document tmail watch
- Correct the watch notes on rules, output, stderr and stopping
- Describe the service lifecycle
- scripting: Say what gpg.has_key does and does not tell
- compose: Document the auto contract and the unusable sender key
- Split the README into a short README and a docs folder
- Rewrite the README and the docs pages for clarity