Skip to content

Feature/signatures and folders per account#19

Open
notthatjesus wants to merge 8 commits into
ssp-data:mainfrom
notthatjesus:feature/signatures-and-folders-per-account
Open

Feature/signatures and folders per account#19
notthatjesus wants to merge 8 commits into
ssp-data:mainfrom
notthatjesus:feature/signatures-and-folders-per-account

Conversation

@notthatjesus
Copy link
Copy Markdown
Contributor

What is it about

Support per account overrides of signatures and folders.

Signatures

This aims to support #16.

Config

A new block is supported under [[accounts]] called [accounts.signature_block]. If present, it will override the default signature block under [ui] and the legacy signature.
Example:

[[accounts]]
name = "Personal"
#... rest of configuration

[accounts.signature_block]
text = "*This is my Personal account signature*"
html = "<b>This is my personal HTML signature</b>"

New signature resolver method

A new method to resolve the signature is added to Config:

func (c *Config) Signature(a AccountConfig) SignatureConfig {
	if a.Signature.Text != "" || a.Signature.HTML != "" {
		return a.Signature
	}
	if c.UI.SignatureBlock.Text != "" || c.UI.SignatureBlock.HTML != "" {
		return c.UI.SignatureBlock
	}
	return SignatureConfig{Text: c.UI.Signature}
}

It returns a SignatureConfig with both text and html so the caller can decide which one to use.
Because account signature depends on the active account, the caller must pass the current active account.
The previous methods created in the UI to get the signature have been removed as this is covered directly by the new Config.Signature method.

Callers replacement

All the callers exist in model.go. These have been refactored to use the new Signature method.

Testing

I have added additional test cases to cover the resolution of the signature as well as the fall back to defaults.

Breaking changes

None. It fully supports previous configurations.

Folders

This supports overriding the name of the folders per-account. This feature is required due to the different naming between IMAP providers like Gmail using tags or Microsoft calling Sent folder Sent Items instead. When using multiple accounts, this is an issue.
This has been a bit more complex change due to two key points:

  • The folders implementation was wired to the UI and not dependent on the active account.
  • Folders are used all over the code, having to refactor many files.

Config file

A new block is supported under [[accounts]] called [accounts.folders]. If present, it will override the default [folders] for any specific folder defined. Currently only the following folders are possible to override:

  • Sent
  • Trash
  • Drafts
  • Spam

Example:

[[accounts]]
name = "Personal"
# ... rest of the config
[accounts.folders]
sent = "[Gmail]/Sent Mail"
trash = "[Gmail]/Trash"
drafts = "[Gmail]/Drafts"
spam = "[Gmail]/Spam"

# ...

[folders]
inbox = "INBOX"
sent = "Sent"
trash = "Trash"
drafts = "Drafts"
to_screen = "ToScreen"
feed = "Feed"
papertrail = "PaperTrail"
screened_out = "ScreenedOut"
archive = "Archive"
waiting = "Waiting"
scheduled = "Scheduled"
someday = "Someday"
spam = "Spam"

New Folders type and folders resolver method

A new type was created to represent the folders supported under the AccountConfig:

type AccountFoldersConfig struct {
	Sent   string `toml:"sent"`
	Trash  string `toml:"trash"`
	Drafts string `toml:"drafts"`
	Spam   string `toml:"spam"`
}

Additionally a new method is created under Config to resolve the folders for the account:

func (c *Config) ResolveFolders(a AccountConfig) FoldersConfig {
	out := c.Folders
	if a.Folders.Sent != "" {
		out.Sent = a.Folders.Sent
	}
	if a.Folders.Trash != "" {
		out.Trash = a.Folders.Trash
	}
	if a.Folders.Drafts != "" {
		out.Drafts = a.Folders.Drafts
	}
	if a.Folders.Spam != "" {
		out.Spam = a.Folders.Spam
	}
	return out
}

This resolver, given the account, will read the [folders] block and then override any folder that has been defined under [accounts.folders].

Callers replacement

This is quite a wide change given that folders are referenced in many places. I'll explain below the approach to this:

  • Most of the callers, while not explicitly defined it, need to know what is the active account. Because the new feature requires it, the caller have been wrapped around ResolveFolders(), as an example, activeFolder().
  • All M, gX motions as well as :go- commands have been refactored to go through the resolver.
  • Trash and spam specific motions have been refactored to use the resolver.
  • saveDraftCmd is modified to take config.AccountConfig instead of *imap.Client
  • Fixed a small bug when switching between accounts as the active account was not passed properly.

Testing

I have covered all the folders affected and callers to make sure nothing breaks. Additionally ran smoke tests with two accounts and different folders setup. All seem OK but I'd appreciate to double check.

Breaking changes

None. Previous configuration is fully supported.

Notes

Fixed a small bug in the activeFolder() tab-labels. Drafts were missing.

@sspaeti
Copy link
Copy Markdown
Member

sspaeti commented May 18, 2026

Love it, thank you so much ! I'm a little short in time, but I will def. merge that

@notthatjesus
Copy link
Copy Markdown
Contributor Author

Love it, thank you so much ! I'm a little short in time, but I will def. merge that

Take your time! You may want to have a look at the last commit. I thought it was a bug but I think you did that intentionally. However, the behavior was a bit odd, it was showing it was in Drafts while the inbox content was displayed.

@sspaeti
Copy link
Copy Markdown
Member

sspaeti commented May 19, 2026

Love it, thank you so much ! I'm a little short in time, but I will def. merge that

Take your time! You may want to have a look at the last commit. I thought it was a bug but I think you did that intentionally. However, the behavior was a bit odd, it was showing it was in Drafts while the inbox content was displayed.

Ahh yes, that was intentional that Draft and Spam do not show up as regular tabs (to make it minimal and to the most important folders). But you can always go to drafts with gd (or move Md) or to Spam with gS (or move/mark spam with $).

I think it's a bit hidden, tough once you know, or check the ? (help), you will find it. Hope that make sense? 😃

…to resolve the Signature either from the account or the UI. Replaced the calls to the signature in model.go
…sh/Drafts/Spam. Added Config.ResolveFolders to merge the account block onto the global folders field-by-field. Added unit tests pinning the field-by-field semantics.
…Added sentDraftsIMAPAccount() as a single resolver for the account whose Sent/Drafts mailboxes receive a send or draft save, so the IMAP client and the folder name can never drift apart under per-account folder configs. Removed the now-unused sentDraftsIMAPClient and presendIMAPClient wrappers.
…pam/Trash moves, screener classifications, batch delete, and folder-comparison checks now use the active account's folder names so per-account overrides take effect. Made activeAccount() safe against zero-account Models so the new per-account lookups don't panic in unit tests that hand-build minimal models.
…older() now returns the per-account IMAP mailbox name for the active account so the existing Sent/Trash/isDraft comparisons resolve correctly under per-account overrides. Migrated the M-chord bulk move map, gS/gd jump chords, the :go-spam cmdline command, and the newInboxList constructor's Sent/Drafts strings. Added a test pinning activeFolder() per-account override behavior. Inbox list delegate still bakes Sent/Drafts at construction time and is not refreshed on account switch — pending follow-up.
…nt/Drafts folder names take effect. Added emailDelegateForActiveAccount() that builds the row-rendering delegate from the active account's resolved folder names, and refreshInboxDelegate() that swaps it into the list. Wired the refresh into the ctrl+a handler. Without this, viewing Sent/Drafts on a non-default account showed the sender instead of the recipient because the delegate still held the initial account's folder names. Added a unit test pinning the delegate-construction logic.
@notthatjesus notthatjesus force-pushed the feature/signatures-and-folders-per-account branch from 5bd8f2e to a79add1 Compare May 22, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants