From 0880d5a8f656ac4f6b3c27d2d10b187ea5a70d3f Mon Sep 17 00:00:00 2001 From: Drew Smirnoff Date: Thu, 30 Apr 2026 22:11:54 +0400 Subject: [PATCH] fix: add catch-all support (#1208) ## What? Ports catch-all support from GitLab ## Why? Requested in #1201 and #1207 Co-authored-by: Steve Evans Co-authored-by: Andriy Chernov --- config/cache.go | 1 + config/config.go | 10 ++++++ config/config_test.go | 11 +++++++ fetcher/fetcher.go | 36 ++++++++++++--------- main.go | 11 ++++++- tui/composer.go | 67 ++++++++++++++++++++++++++++++++++++---- tui/inbox.go | 31 +++++++++++++------ tui/login.go | 24 +++++++++++--- tui/messages.go | 3 ++ tui/settings_accounts.go | 4 +++ 10 files changed, 163 insertions(+), 35 deletions(-) diff --git a/config/cache.go b/config/cache.go index e3677f7f7260bfab2fbdfd852b6c7a898113e157..13a941fb5e657abededba8b1ae25c2af991c53cb 100644 --- a/config/cache.go +++ b/config/cache.go @@ -260,6 +260,7 @@ type Draft struct { Body string `json:"body"` AttachmentPaths []string `json:"attachment_paths,omitempty"` AccountID string `json:"account_id"` + FromOverride string `json:"from_override,omitempty"` InReplyTo string `json:"in_reply_to,omitempty"` References []string `json:"references,omitempty"` QuotedText string `json:"quoted_text,omitempty"` diff --git a/config/config.go b/config/config.go index e2b9ed3def0d9cff4d55213a84762d341cbf878b..0e6bb581635d24db72831732316afa72d7454bb4 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,9 @@ type Account struct { // SendAsEmail controls the visible From header on outgoing mail. // If empty, it defaults to FetchEmail, then Email. SendAsEmail string `json:"send_as_email,omitempty"` + // CatchAll skips per-address filtering so all inbox messages are shown, + // regardless of which address they were delivered to. + CatchAll bool `json:"catch_all,omitempty"` // Custom server settings (used when ServiceProvider is "custom") IMAPServer string `json:"imap_server,omitempty"` @@ -243,6 +246,9 @@ func (a *Account) GetSendAsEmail() string { // FormatFromHeader returns the display-ready From header value. func (a *Account) FormatFromHeader() string { sendAs := a.GetSendAsEmail() + if strings.Contains(sendAs, "<") && strings.Contains(sendAs, ">") { + return sendAs + } if a.Name != "" && sendAs != "" { return fmt.Sprintf("%s <%s>", a.Name, sendAs) } @@ -373,6 +379,7 @@ type secureDiskAccount struct { JMAPEndpoint string `json:"jmap_endpoint,omitempty"` POP3Server string `json:"pop3_server,omitempty"` POP3Port int `json:"pop3_port,omitempty"` + CatchAll bool `json:"catch_all,omitempty"` } type secureDiskConfig struct { @@ -456,6 +463,7 @@ func SaveConfig(config *Config) error { JMAPEndpoint: acc.JMAPEndpoint, POP3Server: acc.POP3Server, POP3Port: acc.POP3Port, + CatchAll: acc.CatchAll, }) } data, err = json.MarshalIndent(sdc, "", " ") @@ -517,6 +525,7 @@ func LoadConfig() (*Config, error) { JMAPEndpoint string `json:"jmap_endpoint,omitempty"` POP3Server string `json:"pop3_server,omitempty"` POP3Port int `json:"pop3_port,omitempty"` + CatchAll bool `json:"catch_all,omitempty"` } type diskConfig struct { Accounts []rawAccount `json:"accounts"` @@ -588,6 +597,7 @@ func LoadConfig() (*Config, error) { JMAPEndpoint: rawAcc.JMAPEndpoint, POP3Server: rawAcc.POP3Server, POP3Port: rawAcc.POP3Port, + CatchAll: rawAcc.CatchAll, } // Validate PGPKeySource diff --git a/config/config_test.go b/config/config_test.go index 9d7bd5d789e44045c632928fe10e708a0f7f6ccf..87d602feafb27a2184a3aa2bef5e817e26d81be2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -41,6 +41,7 @@ func TestSaveAndLoadConfig(t *testing.T) { IMAPPort: 993, SMTPServer: "smtp.custom.com", SMTPPort: 587, + CatchAll: true, }, }, } @@ -312,6 +313,16 @@ func TestAccountSendIdentityHelpers(t *testing.T) { t.Fatalf("GetSendAsEmail() = %q, want %q", got, "login@gmail.com") } }) + + t.Run("format from header avoids double wrapping", func(t *testing.T) { + account := Account{ + Name: "Account Name", + SendAsEmail: "Custom Name ", + } + if got := account.FormatFromHeader(); got != "Custom Name " { + t.Fatalf("FormatFromHeader() = %q, want %q", got, "Custom Name ") + } + }) } func TestTranslateDateFormat(t *testing.T) { diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index 4bf1ef79c5fa493a0ded298c495c24b41ff2a2e1..5cf6bac65b202a9f6ae5cf8846c8e7585387b09a 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -498,7 +498,9 @@ func FetchMailboxEmails(account *config.Account, mailbox string, limit, offset u } matched := false - if isSentMailbox { + if account.CatchAll { + matched = true + } else if isSentMailbox { var senderEmail string if len(msg.Envelope.From) > 0 { senderEmail = msg.Envelope.From[0].Addr() @@ -1518,23 +1520,27 @@ func FetchArchiveEmails(account *config.Account, limit, offset uint32) ([]Email, // For archive/All Mail, match emails where user is sender OR recipient matched := false - // Check if user is the sender - if addressMatches(fromAddr, fetchEmail, account) { + if account.CatchAll { matched = true - } - // Check if user is a recipient - if !matched { - for _, r := range toAddrList { - if addressMatches(r, fetchEmail, account) { - matched = true - break + } else { + // Check if user is the sender + if addressMatches(fromAddr, fetchEmail, account) { + matched = true + } + // Check if user is a recipient + if !matched { + for _, r := range toAddrList { + if addressMatches(r, fetchEmail, account) { + matched = true + break + } } } - } - // Check delivery headers for auto-forwarded emails - if !matched { - headerData := msg.FindBodySection(deliveryHeaderSection) - matched = deliveryHeadersMatch(headerData, fetchEmail, account) + // Check delivery headers for auto-forwarded emails + if !matched { + headerData := msg.FindBodySection(deliveryHeaderSection) + matched = deliveryHeadersMatch(headerData, fetchEmail, account) + } } if !matched { diff --git a/main.go b/main.go index 6c5d65b89af74c054736b9f0558dc71d59f041ac..21650a2d8acbf75eabab6ce22b7bad1fd9e79796 100644 --- a/main.go +++ b/main.go @@ -345,6 +345,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ServiceProvider: msg.Provider, FetchEmail: fetchEmails[0], SendAsEmail: msg.SendAsEmail, + CatchAll: msg.CatchAll, AuthMethod: msg.AuthMethod, Protocol: msg.Protocol, Insecure: msg.Insecure, @@ -389,6 +390,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ServiceProvider: msg.Provider, FetchEmail: fe, SendAsEmail: msg.SendAsEmail, + CatchAll: msg.CatchAll, AuthMethod: msg.AuthMethod, Protocol: msg.Protocol, JMAPEndpoint: msg.JMAPEndpoint, @@ -1066,7 +1068,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { hideTips = m.config.HideTips } login := tui.NewLogin(hideTips) - login.SetEditMode(msg.AccountID, msg.Protocol, msg.Provider, msg.Name, msg.Email, msg.FetchEmail, msg.SendAsEmail, msg.IMAPServer, msg.IMAPPort, msg.SMTPServer, msg.SMTPPort, msg.Insecure, msg.JMAPEndpoint, msg.POP3Server, msg.POP3Port) + login.SetEditMode(msg.AccountID, msg.Protocol, msg.Provider, msg.Name, msg.Email, msg.FetchEmail, msg.SendAsEmail, msg.IMAPServer, msg.IMAPPort, msg.SMTPServer, msg.SMTPPort, msg.Insecure, msg.JMAPEndpoint, msg.POP3Server, msg.POP3Port, msg.CatchAll) m.current = login m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height}) return m, m.current.Init() @@ -2409,6 +2411,13 @@ func sendEmail(account *config.Account, msg tui.SendEmailMsg) tea.Cmd { return tui.EmailResultMsg{Err: fmt.Errorf("no account configured")} } + // Apply custom From address for catch-all accounts. + if msg.FromOverride != "" { + acc := *account + acc.SendAsEmail = msg.FromOverride + account = &acc + } + recipients := splitEmails(msg.To) cc := splitEmails(msg.Cc) bcc := splitEmails(msg.Bcc) diff --git a/tui/composer.go b/tui/composer.go index 18952c0db4ae85acce3fb14708ca59a694c2ed65..4d07466e4a177c4faa25dd5c3de31b82ba3488bb 100644 --- a/tui/composer.go +++ b/tui/composer.go @@ -64,6 +64,7 @@ type Composer struct { accounts []config.Account selectedAccountIdx int showAccountPicker bool + fromInput textinput.Model // editable From when account is catch-all // Contact suggestions suggestions []config.Contact @@ -141,6 +142,12 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer { m.signatureInput.SetStyles(taStyles) m.updateSignature() + m.fromInput = textinput.New() + m.fromInput.Placeholder = t("composer.from_placeholder") + m.fromInput.Prompt = "> " + m.fromInput.CharLimit = 256 + m.fromInput.SetStyles(tiStyles) + // Start focus on To field (From is selectable but not a text input) m.focusIndex = focusTo m.toInput.Focus() @@ -151,10 +158,17 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer { // updateSignature updates the signature input based on the current selected account. func (m *Composer) updateSignature() { if len(m.accounts) > 0 && m.selectedAccountIdx < len(m.accounts) { - if sig, err := config.LoadSignatureForAccount(&m.accounts[m.selectedAccountIdx]); err == nil && sig != "" { + acc := &m.accounts[m.selectedAccountIdx] + if sig, err := config.LoadSignatureForAccount(acc); err == nil && sig != "" { m.signatureInput.SetValue(sig) - return + } else if sig, err := config.LoadSignature(); err == nil && sig != "" { + m.signatureInput.SetValue(sig) + } else { + m.signatureInput.SetValue("") } + // Seed the editable From address for catch-all accounts. + m.fromInput.SetValue(acc.FormatFromHeader()) + return } if sig, err := config.LoadSignature(); err == nil && sig != "" { @@ -197,6 +211,13 @@ func (m *Composer) getFromAddress() string { return "" } +func (m *Composer) isCatchAllAccount() bool { + if len(m.accounts) > 0 && m.selectedAccountIdx < len(m.accounts) { + return m.accounts[m.selectedAccountIdx].CatchAll + } + return false +} + func (m *Composer) getSelectedAccount() *config.Account { if len(m.accounts) > 0 && m.selectedAccountIdx < len(m.accounts) { return &m.accounts[m.selectedAccountIdx] @@ -385,8 +406,8 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { maxFocus := focusSend minFocus := focusFrom - // Skip From field if only one account (nothing to switch) - if len(m.accounts) <= 1 { + // Skip From field if only one non-catch-all account (nothing to switch or edit) + if len(m.accounts) <= 1 && !m.isCatchAllAccount() { minFocus = focusTo } @@ -396,6 +417,7 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.focusIndex = maxFocus } + m.fromInput.Blur() m.toInput.Blur() m.ccInput.Blur() m.bccInput.Blur() @@ -404,6 +426,10 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.signatureInput.Blur() switch m.focusIndex { + case focusFrom: + if m.isCatchAllAccount() { + cmds = append(cmds, m.fromInput.Focus()) + } case focusTo: cmds = append(cmds, m.toInput.Focus()) case focusCc: @@ -428,8 +454,12 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter", " ": switch m.focusIndex { case focusFrom: - if len(m.accounts) > 1 && msg.String() == "enter" { + if msg.String() == "enter" && len(m.accounts) > 1 { m.showAccountPicker = true + return m, nil + } + if m.isCatchAllAccount() && msg.String() == " " { + break } return m, nil case focusAttachment: @@ -449,6 +479,10 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if acc != nil { accountID = acc.ID } + fromOverride := "" + if m.isCatchAllAccount() { + fromOverride = m.fromInput.Value() + } return m, func() tea.Msg { return SendEmailMsg{ To: m.toInput.Value(), @@ -458,6 +492,7 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { Body: m.bodyInput.Value(), AttachmentPaths: m.attachmentPaths, AccountID: accountID, + FromOverride: fromOverride, QuotedText: m.quotedText, InReplyTo: m.inReplyTo, References: m.references, @@ -473,6 +508,11 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } switch m.focusIndex { + case focusFrom: + if m.isCatchAllAccount() { + m.fromInput, cmd = m.fromInput.Update(msg) + cmds = append(cmds, cmd) + } case focusTo: m.toInput, cmd = m.toInput.Update(msg) cmds = append(cmds, cmd) @@ -528,7 +568,18 @@ func (m *Composer) View() tea.View { // From field with account selector fromAddr := m.getFromAddress() var fromField string - if len(m.accounts) > 1 { + if m.isCatchAllAccount() { + fromAddrView := m.fromInput.View() + if len(m.accounts) > 1 { + if m.focusIndex == focusFrom { + fromField = focusedStyle.Render(fmt.Sprintf("> %s ", t("composer.from"))) + fromAddrView + " " + blurredStyle.Render("["+t("composer.enter_to_switch")+"]") + } else { + fromField = blurredStyle.Render(fmt.Sprintf(" %s ", t("composer.from"))) + fromAddrView + " " + blurredStyle.Render("["+t("composer.switchable")+"]") + } + } else { + fromField = " " + t("composer.from") + " " + fromAddrView + } + } else if len(m.accounts) > 1 { if m.focusIndex == focusFrom { fromField = focusedStyle.Render(fmt.Sprintf("> %s %s [%s]", t("composer.from"), fromAddr, t("composer.enter_to_switch"))) } else { @@ -876,6 +927,7 @@ func (m *Composer) ToDraft() config.Draft { Body: m.bodyInput.Value(), AttachmentPaths: m.attachmentPaths, AccountID: m.GetSelectedAccountID(), + FromOverride: m.fromInput.Value(), InReplyTo: m.inReplyTo, References: m.references, QuotedText: m.quotedText, @@ -889,6 +941,9 @@ func NewComposerFromDraft(draft config.Draft, accounts []config.Account, hideTip m.bccInput.SetValue(draft.Bcc) m.draftID = draft.ID m.attachmentPaths = draft.AttachmentPaths + if m.isCatchAllAccount() && draft.FromOverride != "" { + m.fromInput.SetValue(draft.FromOverride) + } m.inReplyTo = draft.InReplyTo m.references = draft.References m.quotedText = draft.QuotedText diff --git a/tui/inbox.go b/tui/inbox.go index bbd28b35cbeea815dd447f736b5f4a21c67c0eae..9faa01c46bfcdf3b45f50d206d30d23d74fd45f8 100644 --- a/tui/inbox.go +++ b/tui/inbox.go @@ -389,16 +389,18 @@ func (m *Inbox) updateList() { currentIndex := m.list.Index() displayEmails := m.displayEmails() - var showAccountLabel bool + m.emailsCount = len(displayEmails) + var showAccountLabel bool if m.searchActive { - showAccountLabel = !(len(m.accounts) <= 1) + showAccountLabel = len(m.accounts) > 1 } else if m.currentAccountID == "" { - // "ALL" view - show all emails sorted by date - showAccountLabel = !(len(m.accounts) <= 1) + showAccountLabel = len(m.accounts) > 1 } - m.emailsCount = len(displayEmails) + if !showAccountLabel && len(m.accounts) == 1 && m.accounts[0].CatchAll { + showAccountLabel = true + } items := make([]list.Item, len(displayEmails)) for i, email := range displayEmails { @@ -500,6 +502,18 @@ func (m *Inbox) filteredSearchResults() []fetcher.Email { } func (m *Inbox) accountLabelForEmail(email fetcher.Email) string { + var owningAcc *config.Account + for i := range m.accounts { + if m.accounts[i].ID == email.AccountID { + owningAcc = &m.accounts[i] + break + } + } + + if owningAcc != nil && owningAcc.CatchAll && len(email.To) > 0 { + return extractEmailAddress(email.To[0]) + } + for _, acc := range m.accounts { fetchEmail := accountDisplayEmail(acc) for _, recipient := range email.To { @@ -508,10 +522,9 @@ func (m *Inbox) accountLabelForEmail(email fetcher.Email) string { } } } - for _, acc := range m.accounts { - if acc.ID == email.AccountID { - return accountDisplayEmail(acc) - } + + if owningAcc != nil { + return accountDisplayEmail(*owningAcc) } return "" } diff --git a/tui/login.go b/tui/login.go index ee7c3d1916320b4e112bc066a32bd2bd752bb464..bd51034d5846bce8abfc74b6f6d711cc2927bbc4 100644 --- a/tui/login.go +++ b/tui/login.go @@ -35,6 +35,7 @@ const ( inputSMTPServer inputSMTPPort inputInsecure + inputCatchAll // "true/false" — show all inbox messages regardless of To address inputJMAPEndpoint // JMAP session URL inputPOP3Server inputPOP3Port @@ -97,6 +98,9 @@ func NewLogin(hideTips bool) *Login { case inputInsecure: t.Placeholder = "Insecure (true/false) - Skip TLS verification" t.Prompt = "🔓 > " + case inputCatchAll: + t.Placeholder = "Catch-All (true/false) - Show all inbox messages" + t.Prompt = "📬 > " case inputJMAPEndpoint: t.Placeholder = "JMAP Session URL (e.g., https://api.fastmail.com/jmap/session)" t.Prompt = "🔗 > " @@ -139,14 +143,14 @@ func (m *Login) visibleFields() []int { switch proto { case "jmap": // JMAP: no provider selector, just endpoint + common fields - fields = append(fields, inputName, inputEmail, inputFetchEmail, inputSendAsEmail, inputPassword, inputJMAPEndpoint) + fields = append(fields, inputName, inputEmail, inputFetchEmail, inputSendAsEmail, inputCatchAll, inputPassword, inputJMAPEndpoint) case "pop3": // POP3: custom server fields + SMTP for sending - fields = append(fields, inputName, inputEmail, inputFetchEmail, inputSendAsEmail, inputPassword, + fields = append(fields, inputName, inputEmail, inputFetchEmail, inputSendAsEmail, inputCatchAll, inputPassword, inputPOP3Server, inputPOP3Port, inputSMTPServer, inputSMTPPort, inputInsecure) default: // IMAP (default): existing flow - fields = append(fields, inputProvider, inputName, inputEmail, inputFetchEmail, inputSendAsEmail) + fields = append(fields, inputProvider, inputName, inputEmail, inputFetchEmail, inputSendAsEmail, inputCatchAll) if hasOAuth { fields = append(fields, inputAuthMethod) } @@ -284,6 +288,7 @@ func (m *Login) submitForm() func() tea.Msg { proto := m.protocol() insecure := m.inputs[inputInsecure].Value() == "true" + catchAll := m.inputs[inputCatchAll].Value() == "true" return func() tea.Msg { return Credentials{ @@ -293,6 +298,7 @@ func (m *Login) submitForm() func() tea.Msg { Host: m.inputs[inputEmail].Value(), FetchEmail: m.inputs[inputFetchEmail].Value(), SendAsEmail: m.inputs[inputSendAsEmail].Value(), + CatchAll: catchAll, Password: m.inputs[inputPassword].Value(), IMAPServer: m.inputs[inputIMAPServer].Value(), IMAPPort: imapPort, @@ -344,6 +350,8 @@ func (m *Login) View() tea.View { tip = "The port for the SMTP server (usually 587 for TLS)." case inputInsecure: tip = "Type 'true' to disable TLS certificate verification (not recommended)." + case inputCatchAll: + tip = "Type 'true' to show all inbox messages regardless of To address (useful for catch-all domains)." case inputJMAPEndpoint: tip = "The JMAP session resource URL (e.g., https://api.fastmail.com/jmap/session)." case inputPOP3Server: @@ -366,6 +374,7 @@ func (m *Login) View() tea.View { m.inputs[inputEmail].View(), m.inputs[inputFetchEmail].View(), m.inputs[inputSendAsEmail].View(), + m.inputs[inputCatchAll].View(), m.inputs[inputPassword].View(), "", listHeader.Render("JMAP Settings:"), @@ -377,6 +386,7 @@ func (m *Login) View() tea.View { m.inputs[inputEmail].View(), m.inputs[inputFetchEmail].View(), m.inputs[inputSendAsEmail].View(), + m.inputs[inputCatchAll].View(), m.inputs[inputPassword].View(), "", listHeader.Render("POP3 Server Settings:"), @@ -398,6 +408,7 @@ func (m *Login) View() tea.View { m.inputs[inputEmail].View(), m.inputs[inputFetchEmail].View(), m.inputs[inputSendAsEmail].View(), + m.inputs[inputCatchAll].View(), ) if hasOAuth { @@ -438,7 +449,7 @@ func (m *Login) View() tea.View { } // SetEditMode sets the login form to edit an existing account. -func (m *Login) SetEditMode(accountID, protocol, provider, name, email, fetchEmail, sendAsEmail, imapServer string, imapPort int, smtpServer string, smtpPort int, insecure bool, jmapEndpoint, pop3Server string, pop3Port int) { +func (m *Login) SetEditMode(accountID, protocol, provider, name, email, fetchEmail, sendAsEmail, imapServer string, imapPort int, smtpServer string, smtpPort int, insecure bool, jmapEndpoint, pop3Server string, pop3Port int, catchAll bool) { m.isEditMode = true m.accountID = accountID @@ -451,6 +462,11 @@ func (m *Login) SetEditMode(accountID, protocol, provider, name, email, fetchEma m.inputs[inputEmail].SetValue(email) m.inputs[inputFetchEmail].SetValue(fetchEmail) m.inputs[inputSendAsEmail].SetValue(sendAsEmail) + if catchAll { + m.inputs[inputCatchAll].SetValue("true") + } else { + m.inputs[inputCatchAll].SetValue("false") + } m.showCustom = provider == "custom" if m.showCustom { diff --git a/tui/messages.go b/tui/messages.go index b1cda5e454260003db3e3f9def60c8855cb48cda..7457eee5fc140c04ea062eff93635ca151c30907 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -35,6 +35,7 @@ type SendEmailMsg struct { InReplyTo string References []string AccountID string // ID of the account to send from + FromOverride string // Custom From address (used when account is catch-all) QuotedText string // Hidden quoted text appended when sending Signature string // Signature to append to email body SignSMIME bool // Whether to sign the email using S/MIME @@ -48,6 +49,7 @@ type Credentials struct { Host string // Host (this was the previous "Email Address" field in the UI) FetchEmail string // Single email address to fetch messages for. If empty, code should default this to Host when creating the account. SendAsEmail string // Optional From header email. If empty, sending falls back to FetchEmail, then Host. + CatchAll bool // Show all inbox messages regardless of To address. Password string IMAPServer string IMAPPort int @@ -277,6 +279,7 @@ type GoToEditAccountMsg struct { Email string FetchEmail string SendAsEmail string + CatchAll bool IMAPServer string IMAPPort int SMTPServer string diff --git a/tui/settings_accounts.go b/tui/settings_accounts.go index 23380909303cb03bd7ffd6bba413fb62d5b01de0..7ca07f3a1442a4893d6448c840334189de4e0291 100644 --- a/tui/settings_accounts.go +++ b/tui/settings_accounts.go @@ -59,6 +59,7 @@ func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { Email: acc.Email, FetchEmail: acc.FetchEmail, SendAsEmail: acc.SendAsEmail, + CatchAll: acc.CatchAll, IMAPServer: acc.IMAPServer, IMAPPort: acc.IMAPPort, SMTPServer: acc.SMTPServer, @@ -147,6 +148,9 @@ func (m *Settings) viewAccounts() string { if config.HasAccountSignature(&account) { providerInfo += " [Signature]" } + if account.CatchAll { + providerInfo += " [Catch-All]" + } line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))