feat: per-account email signatures (#847)

Daniel Purnomo and drew created

Co-authored-by: drew <me@andrinoff.com>

Change summary

config/config.go         |   3 
config/signature.go      |  79 +++++++++++++++++++++-
i18n/locales/ar.json     |   2 
i18n/locales/de.json     |   2 
i18n/locales/en.json     |   2 
i18n/locales/es.json     |   2 
i18n/locales/fr.json     |   2 
i18n/locales/ja.json     |   2 
i18n/locales/pl.json     |   2 
i18n/locales/pt.json     |   2 
i18n/locales/ru.json     |   2 
i18n/locales/uk.json     |   2 
i18n/locales/zh.json     |   2 
main.go                  |   2 
tui/composer.go          |  26 ++++++-
tui/messages.go          |   4 
tui/settings_accounts.go |   8 ++
tui/settings_general.go  | 148 ++++++++++++++++++++++++++---------------
tui/signature.go         |  28 +++++--
19 files changed, 237 insertions(+), 83 deletions(-)

Detailed changes

config/config.go 🔗

@@ -70,6 +70,9 @@ type Account struct {
 	JMAPEndpoint string `json:"jmap_endpoint,omitempty"` // JMAP session URL (for protocol=jmap)
 	POP3Server   string `json:"pop3_server,omitempty"`   // POP3 server hostname (for protocol=pop3)
 	POP3Port     int    `json:"pop3_port,omitempty"`     // POP3 server port (for protocol=pop3)
+
+	// Per-account signature (overrides global signature)
+	Signature string `json:"signature,omitempty"`
 }
 
 // MailingList represents a named group of email addresses.

config/signature.go 🔗

@@ -5,7 +5,7 @@ import (
 	"path/filepath"
 )
 
-// signatureFile returns the full path to the signature file.
+// signatureFile returns the full path to the global signature file.
 func signatureFile() (string, error) {
 	dir, err := configDir()
 	if err != nil {
@@ -14,7 +14,16 @@ func signatureFile() (string, error) {
 	return filepath.Join(dir, "signature.txt"), nil
 }
 
-// LoadSignature loads the signature from the signature file.
+// accountSignatureFile returns the path to the per-account signature file.
+func accountSignatureFile(accountID string) (string, error) {
+	dir, err := configDir()
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(dir, "signatures", accountID+".txt"), nil
+}
+
+// LoadSignature loads the signature from the global signature file.
 func LoadSignature() (string, error) {
 	path, err := signatureFile()
 	if err != nil {
@@ -30,7 +39,43 @@ func LoadSignature() (string, error) {
 	return string(data), nil
 }
 
-// SaveSignature saves the signature to the signature file.
+// LoadRawAccountSignature loads the per-account signature if one exists,
+// without falling back to the global signature.
+func LoadRawAccountSignature(account *Account) (string, error) {
+	if account == nil || account.ID == "" {
+		return "", nil
+	}
+
+	// Check for per-account signature file first
+	path, err := accountSignatureFile(account.ID)
+	if err != nil {
+		return "", err
+	}
+	data, err := SecureReadFile(path)
+	if err == nil && len(data) > 0 {
+		return string(data), nil
+	}
+
+	// Fall back to inline account signature
+	if account.Signature != "" {
+		return account.Signature, nil
+	}
+
+	return "", nil
+}
+
+// LoadSignatureForAccount loads the per-account signature if one exists,
+// otherwise falls back to the global signature.
+func LoadSignatureForAccount(account *Account) (string, error) {
+	sig, err := LoadRawAccountSignature(account)
+	if err == nil && sig != "" {
+		return sig, nil
+	}
+	// Fall back to global signature
+	return LoadSignature()
+}
+
+// SaveSignature saves the signature to the global signature file.
 func SaveSignature(signature string) error {
 	path, err := signatureFile()
 	if err != nil {
@@ -42,7 +87,24 @@ func SaveSignature(signature string) error {
 	return SecureWriteFile(path, []byte(signature), 0600)
 }
 
-// HasSignature checks if a signature file exists and is non-empty.
+// SaveSignatureForAccount saves a per-account signature file.
+func SaveSignatureForAccount(accountID, signature string) error {
+	path, err := accountSignatureFile(accountID)
+	if err != nil {
+		return err
+	}
+	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
+		return err
+	}
+	if signature == "" {
+		// Remove the file to fall back to global
+		os.Remove(path)
+		return nil
+	}
+	return SecureWriteFile(path, []byte(signature), 0600)
+}
+
+// HasSignature checks if a global signature file exists and is non-empty.
 func HasSignature() bool {
 	sig, err := LoadSignature()
 	if err != nil {
@@ -50,3 +112,12 @@ func HasSignature() bool {
 	}
 	return sig != ""
 }
+
+// HasAccountSignature checks if an account has its own signature (file or inline).
+func HasAccountSignature(account *Account) bool {
+	sig, err := LoadRawAccountSignature(account)
+	if err != nil {
+		return false
+	}
+	return sig != ""
+}

i18n/locales/ar.json 🔗

@@ -123,7 +123,7 @@
       "title": "إعدادات الحسابات",
       "no_accounts": "لم يتم تكوين حسابات.",
       "add_account": "إضافة حساب جديد",
-      "help": "↑/↓: التنقل • enter: تعديل إعدادات التشفير • e: تعديل الخادم • d: حذف"
+      "help": "↑/↓: التنقل • enter: تعديل إعدادات التشفير • e: تعديل الخادم • s: تعديل التوقيع • d: حذف"
     },
     "settings_theme": {
       "title": "المظهر",

i18n/locales/de.json 🔗

@@ -121,7 +121,7 @@
       "title": "Kontoeinstellungen",
       "no_accounts": "Keine Konten konfiguriert.",
       "add_account": "Neues Konto Hinzufügen",
-      "help": "↑/↓: navigieren • enter: Krypto-Konfig. bearbeiten • e: Server bearbeiten • d: löschen"
+      "help": "↑/↓: navigieren • enter: Krypto-Konfig. bearbeiten • e: Server bearbeiten • s: Signatur bearbeiten • d: löschen"
     },
     "settings_theme": {
       "title": "Design",

i18n/locales/en.json 🔗

@@ -121,7 +121,7 @@
       "title": "Account Settings",
       "no_accounts": "No accounts configured.",
       "add_account": "Add New Account",
-      "help": "↑/↓: navigate • enter: edit crypto config • e: edit server • d: delete"
+      "help": "↑/↓: navigate • enter: edit crypto config • e: edit server • s: edit signature • d: delete"
     },
     "settings_theme": {
       "title": "Theme",

i18n/locales/es.json 🔗

@@ -121,7 +121,7 @@
       "title": "Configuración de Cuentas",
       "no_accounts": "No hay cuentas configuradas.",
       "add_account": "Agregar Nueva Cuenta",
-      "help": "↑/↓: navegar • enter: editar config. de cifrado • e: editar servidor • d: eliminar"
+      "help": "↑/↓: navegar • enter: editar config. de cifrado • e: editar servidor • s: editar firma • d: eliminar"
     },
     "settings_theme": {
       "title": "Tema",

i18n/locales/fr.json 🔗

@@ -121,7 +121,7 @@
       "title": "Paramètres des Comptes",
       "no_accounts": "Aucun compte configuré.",
       "add_account": "Ajouter un Nouveau Compte",
-      "help": "↑/↓: naviguer • entrée: modifier config. crypto • e: modifier serveur • d: supprimer"
+      "help": "↑/↓: naviguer • entrée: modifier config. crypto • e: modifier serveur • s: modifier signature • d: supprimer"
     },
     "settings_theme": {
       "title": "Thème",

i18n/locales/ja.json 🔗

@@ -120,7 +120,7 @@
       "title": "アカウント設定",
       "no_accounts": "アカウントが設定されていません。",
       "add_account": "新しいアカウントを追加",
-      "help": "↑/↓: 移動 • enter: 暗号化設定を編集 • e: サーバーを編集 • d: 削除"
+      "help": "↑/↓: 移動 • enter: 暗号化設定を編集 • e: サーバーを編集 • s: 署名を編集 • d: 削除"
     },
     "settings_theme": {
       "title": "テーマ",

i18n/locales/pl.json 🔗

@@ -123,7 +123,7 @@
       "title": "Ustawienia Kont",
       "no_accounts": "Brak skonfigurowanych kont.",
       "add_account": "Dodaj Nowe Konto",
-      "help": "↑/↓: nawigacja • enter: edytuj konfigurację szyfrowania • e: edytuj serwer • d: usuń"
+      "help": "↑/↓: nawigacja • enter: edytuj konfigurację szyfrowania • e: edytuj serwer • s: edytuj podpis • d: usuń"
     },
     "settings_theme": {
       "title": "Motyw",

i18n/locales/pt.json 🔗

@@ -121,7 +121,7 @@
       "title": "Configurações de Contas",
       "no_accounts": "Nenhuma conta configurada.",
       "add_account": "Adicionar Nova Conta",
-      "help": "↑/↓: navegar • enter: editar config. de criptografia • e: editar servidor • d: excluir"
+      "help": "↑/↓: navegar • enter: editar config. de criptografia • e: editar servidor • s: editar assinatura • d: excluir"
     },
     "settings_theme": {
       "title": "Tema",

i18n/locales/ru.json 🔗

@@ -123,7 +123,7 @@
       "title": "Настройки Учётных Записей",
       "no_accounts": "Учётные записи не настроены.",
       "add_account": "Добавить Новую Учётную Запись",
-      "help": "↑/↓: навигация • enter: редактировать конфигурацию шифрования • e: редактировать сервер • d: удалить"
+      "help": "↑/↓: навигация • enter: редактировать конфигурацию шифрования • e: редактировать сервер • s: редактировать подпись • d: удалить"
     },
     "settings_theme": {
       "title": "Тема",

i18n/locales/uk.json 🔗

@@ -122,7 +122,7 @@
       "title": "Налаштування облікових записів",
       "no_accounts": "Облікові записи не налаштовано.",
       "add_account": "Додати новий обліковий запис",
-      "help": "↑/↓: навігація • enter: редагувати криптоконфіг • e: редагувати сервер • d: видалити"
+      "help": "↑/↓: навігація • enter: редагувати криптоконфіг • e: редагувати сервер • s: редагувати підпис • d: видалити"
     },
     "settings_theme": {
       "title": "Тема",

i18n/locales/zh.json 🔗

@@ -120,7 +120,7 @@
       "title": "账户设置",
       "no_accounts": "未配置账户。",
       "add_account": "添加新账户",
-      "help": "↑/↓: 导航 • enter: 编辑加密配置 • e: 编辑服务器 • d: 删除"
+      "help": "↑/↓: 导航 • enter: 编辑加密配置 • e: 编辑服务器 • s: 编辑签名 • d: 删除"
     },
     "settings_theme": {
       "title": "主题",

main.go 🔗

@@ -961,7 +961,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		return m, m.current.Init()
 
 	case tui.GoToSignatureEditorMsg:
-		m.current = tui.NewSignatureEditor()
+		m.current = tui.NewSignatureEditor(msg.AccountID)
 		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
 		return m, m.current.Init()
 

tui/composer.go 🔗

@@ -139,10 +139,7 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer {
 	m.signatureInput.Prompt = "> "
 	m.signatureInput.SetHeight(3)
 	m.signatureInput.SetStyles(taStyles)
-	// Load default signature
-	if sig, err := config.LoadSignature(); err == nil && sig != "" {
-		m.signatureInput.SetValue(sig)
-	}
+	m.updateSignature()
 
 	// Start focus on To field (From is selectable but not a text input)
 	m.focusIndex = focusTo
@@ -151,6 +148,22 @@ func NewComposer(from, to, subject, body string, hideTips bool) *Composer {
 	return m
 }
 
+// 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 != "" {
+			m.signatureInput.SetValue(sig)
+			return
+		}
+	}
+
+	if sig, err := config.LoadSignature(); err == nil && sig != "" {
+		m.signatureInput.SetValue(sig)
+	} else {
+		m.signatureInput.SetValue("")
+	}
+}
+
 // NewComposerWithAccounts initializes a composer with multiple account support.
 func NewComposerWithAccounts(accounts []config.Account, selectedAccountID string, to, subject, body string, hideTips bool) *Composer {
 	m := NewComposer("", to, subject, body, hideTips)
@@ -163,6 +176,7 @@ func NewComposerWithAccounts(accounts []config.Account, selectedAccountID string
 			break
 		}
 	}
+	m.updateSignature()
 
 	return m
 }
@@ -319,10 +333,12 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			case "up", "k":
 				if m.selectedAccountIdx > 0 {
 					m.selectedAccountIdx--
+					m.updateSignature()
 				}
 			case "down", "j":
 				if m.selectedAccountIdx < len(m.accounts)-1 {
 					m.selectedAccountIdx++
+					m.updateSignature()
 				}
 			case "enter":
 				m.showAccountPicker = false
@@ -698,6 +714,7 @@ func (m *Composer) SetAccounts(accounts []config.Account) {
 	if m.selectedAccountIdx >= len(accounts) {
 		m.selectedAccountIdx = 0
 	}
+	m.updateSignature()
 }
 
 // SetSelectedAccount sets the selected account by ID.
@@ -705,6 +722,7 @@ func (m *Composer) SetSelectedAccount(accountID string) {
 	for i, acc := range m.accounts {
 		if acc.ID == accountID {
 			m.selectedAccountIdx = i
+			m.updateSignature()
 			return
 		}
 	}

tui/messages.go 🔗

@@ -102,7 +102,9 @@ type GoToSettingsMsg struct{}
 
 type GoToTrashArchiveMsg struct{}
 
-type GoToSignatureEditorMsg struct{}
+type GoToSignatureEditorMsg struct {
+	AccountID string
+}
 
 type FetchMoreEmailsMsg struct {
 	Offset    uint32

tui/settings_accounts.go 🔗

@@ -7,6 +7,7 @@ import (
 	"charm.land/bubbles/v2/textinput"
 	tea "charm.land/bubbletea/v2"
 	"charm.land/lipgloss/v2"
+	"github.com/floatpane/matcha/config"
 )
 
 func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
@@ -70,6 +71,10 @@ func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 				}
 			}
 		}
+	case "s": // Edit account signature
+		if m.accountsCursor < len(m.cfg.Accounts) {
+			return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: m.cfg.Accounts[m.accountsCursor].ID} }
+		}
 	case "c": // Quick shortcut to crypto config
 		if m.accountsCursor < len(m.cfg.Accounts) {
 			m.enterCryptoConfig()
@@ -139,6 +144,9 @@ func (m *Settings) viewAccounts() string {
 		if account.PGPPublicKey != "" && account.PGPPrivateKey != "" {
 			providerInfo += " [PGP Configured]"
 		}
+		if config.HasAccountSignature(&account) {
+			providerInfo += " [Signature]"
+		}
 
 		line := fmt.Sprintf("%s - %s", displayName, accountEmailStyle.Render(providerInfo))
 

tui/settings_general.go 🔗

@@ -9,58 +9,106 @@ import (
 	"github.com/floatpane/matcha/i18n"
 )
 
+type generalOption struct {
+	labelKey     string
+	value        string
+	tip          string
+	isAccountSig bool
+	accountID    string
+}
+
+func (m *Settings) buildGeneralOptions() []generalOption {
+	opts := []generalOption{
+		{"settings_general.disable_images", onOff(m.cfg.DisableImages), "Prevent images from loading automatically in emails.", false, ""},
+		{"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen.", false, ""},
+		{"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail.", false, ""},
+		{"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed.", false, ""},
+		{"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly.", false, ""},
+		{"settings_general.signature", getSignatureStatus(), "Configure the global signature appended to your outgoing emails.", false, ""},
+	}
+
+	for _, acc := range m.cfg.Accounts {
+		status := t("settings_general.signature_not_configured")
+		accCopy := acc // capture for pointer safety
+		if config.HasAccountSignature(&accCopy) {
+			status = t("settings_general.signature_configured")
+		}
+		opts = append(opts, generalOption{
+			labelKey:     fmt.Sprintf("Signature (%s)", acc.Email),
+			value:        status,
+			tip:          fmt.Sprintf("Configure the signature for %s", acc.Email),
+			isAccountSig: true,
+			accountID:    acc.ID,
+		})
+	}
+
+	return opts
+}
+
 func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
+	opts := m.buildGeneralOptions()
+
 	switch msg.String() {
 	case "up", "k":
 		if m.generalCursor > 0 {
 			m.generalCursor--
 		}
 	case "down", "j":
-		if m.generalCursor < 5 {
+		if m.generalCursor < len(opts)-1 {
 			m.generalCursor++
 		}
 	case "enter", "space", "right", "l":
-		switch m.generalCursor {
-		case 0: // Image Display
-			m.cfg.DisableImages = !m.cfg.DisableImages
-			_ = config.SaveConfig(m.cfg)
-		case 1: // Contextual Tips
-			m.cfg.HideTips = !m.cfg.HideTips
-			_ = config.SaveConfig(m.cfg)
-		case 2: // Desktop Notifications
-			m.cfg.DisableNotifications = !m.cfg.DisableNotifications
-			_ = config.SaveConfig(m.cfg)
-		case 3: // Date Format
-			switch m.cfg.DateFormat {
-			case config.DateFormatEU:
-				m.cfg.DateFormat = config.DateFormatUS
-			case config.DateFormatUS:
-				m.cfg.DateFormat = config.DateFormatISO
-			default: // or ISO
-				m.cfg.DateFormat = config.DateFormatEU
-			}
-			_ = config.SaveConfig(m.cfg)
-		case 4: // Language
-			// Cycle through available languages
-			langs := i18n.LanguageCodes()
-			currentLang := m.cfg.GetLanguage()
-			currentIdx := -1
-			for i, lang := range langs {
-				if lang == currentLang {
-					currentIdx = i
-					break
+		if m.generalCursor < len(opts) {
+			opt := opts[m.generalCursor]
+			if opt.isAccountSig {
+				if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
+					return m, func() tea.Msg { return GoToSignatureEditorMsg{AccountID: opt.accountID} }
 				}
+				return m, nil
 			}
-			nextIdx := (currentIdx + 1) % len(langs)
-			m.cfg.Language = langs[nextIdx]
-			_ = config.SaveConfig(m.cfg)
-			// Apply language change immediately
-			i18n.GetManager().SetLanguage(m.cfg.Language)
-			// Trigger full UI rebuild
-			return m, func() tea.Msg { return LanguageChangedMsg{} }
-		case 5: // Edit Signature
-			if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
-				return m, func() tea.Msg { return GoToSignatureEditorMsg{} }
+
+			switch m.generalCursor {
+			case 0: // Image Display
+				m.cfg.DisableImages = !m.cfg.DisableImages
+				_ = config.SaveConfig(m.cfg)
+			case 1: // Contextual Tips
+				m.cfg.HideTips = !m.cfg.HideTips
+				_ = config.SaveConfig(m.cfg)
+			case 2: // Desktop Notifications
+				m.cfg.DisableNotifications = !m.cfg.DisableNotifications
+				_ = config.SaveConfig(m.cfg)
+			case 3: // Date Format
+				switch m.cfg.DateFormat {
+				case config.DateFormatEU:
+					m.cfg.DateFormat = config.DateFormatUS
+				case config.DateFormatUS:
+					m.cfg.DateFormat = config.DateFormatISO
+				default: // or ISO
+					m.cfg.DateFormat = config.DateFormatEU
+				}
+				_ = config.SaveConfig(m.cfg)
+			case 4: // Language
+				// Cycle through available languages
+				langs := i18n.LanguageCodes()
+				currentLang := m.cfg.GetLanguage()
+				currentIdx := -1
+				for i, lang := range langs {
+					if lang == currentLang {
+						currentIdx = i
+						break
+					}
+				}
+				nextIdx := (currentIdx + 1) % len(langs)
+				m.cfg.Language = langs[nextIdx]
+				_ = config.SaveConfig(m.cfg)
+				// Apply language change immediately
+				i18n.GetManager().SetLanguage(m.cfg.Language)
+				// Trigger full UI rebuild
+				return m, func() tea.Msg { return LanguageChangedMsg{} }
+			case 5: // Edit Signature
+				if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
+					return m, func() tea.Msg { return GoToSignatureEditorMsg{} }
+				}
 			}
 		}
 	}
@@ -72,18 +120,7 @@ func (m *Settings) viewGeneral() string {
 
 	b.WriteString(titleStyle.Render("General Settings") + "\n\n")
 
-	options := []struct {
-		labelKey string
-		value    string
-		tip      string
-	}{
-		{"settings_general.disable_images", onOff(m.cfg.DisableImages), "Prevent images from loading automatically in emails."},
-		{"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen."},
-		{"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail."},
-		{"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed."},
-		{"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly."},
-		{"settings_general.signature", getSignatureStatus(), "Configure the signature appended to your outgoing emails."},
-	}
+	options := m.buildGeneralOptions()
 
 	for i, opt := range options {
 		cursor := "  "
@@ -93,9 +130,12 @@ func (m *Settings) viewGeneral() string {
 			style = selectedAccountItemStyle
 		}
 
-		label := t(opt.labelKey)
+		label := opt.labelKey
+		if !opt.isAccountSig {
+			label = t(opt.labelKey)
+		}
 		text := fmt.Sprintf("%s: %s", label, opt.value)
-		if opt.labelKey == "settings_general.signature" {
+		if opt.labelKey == "settings_general.signature" || opt.isAccountSig {
 			text = fmt.Sprintf("%s (%s)", label, opt.value)
 		}
 

tui/signature.go 🔗

@@ -9,13 +9,14 @@ import (
 
 // SignatureEditor displays the signature editing screen.
 type SignatureEditor struct {
-	textarea textarea.Model
-	width    int
-	height   int
+	textarea  textarea.Model
+	accountID string
+	width     int
+	height    int
 }
 
 // NewSignatureEditor creates a new signature editor model.
-func NewSignatureEditor() *SignatureEditor {
+func NewSignatureEditor(accountID string) *SignatureEditor {
 	ta := textarea.New()
 	ta.Placeholder = "Enter your email signature...\n\nExample:\nBest regards,\nDrew"
 	ta.SetHeight(10)
@@ -23,12 +24,19 @@ func NewSignatureEditor() *SignatureEditor {
 	ta.Focus()
 
 	// Load existing signature
-	if sig, err := config.LoadSignature(); err == nil && sig != "" {
-		ta.SetValue(sig)
+	if accountID != "" {
+		if sig, err := config.LoadRawAccountSignature(&config.Account{ID: accountID}); err == nil && sig != "" {
+			ta.SetValue(sig)
+		}
+	} else {
+		if sig, err := config.LoadSignature(); err == nil && sig != "" {
+			ta.SetValue(sig)
+		}
 	}
 
 	return &SignatureEditor{
-		textarea: ta,
+		textarea:  ta,
+		accountID: accountID,
 	}
 }
 
@@ -56,7 +64,11 @@ func (m *SignatureEditor) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		case "esc":
 			// Save and go back to settings
 			signature := m.textarea.Value()
-			go config.SaveSignature(signature)
+			if m.accountID != "" {
+				go config.SaveSignatureForAccount(m.accountID, signature)
+			} else {
+				go config.SaveSignature(signature)
+			}
 			return m, func() tea.Msg { return GoToSettingsMsg{} }
 		}
 	}