fix: resolve default sender to canonical identity on local-part login

When authenticating with a local-part username (e.g. 'user' instead of
'user@domain.tld') on Stalwart 0.15.x, the default sender could resolve
to an alias identity instead of the canonical mailbox address.

- Add emailMatchesUsername() helper that matches local-part usernames
  against full email addresses (e.g. 'user' matches 'user@domain.tld')
- Prefer canonical identities (mayDelete=false) over aliases as tiebreaker
- Add preferredPrimaryId to identity store (persisted to localStorage)
  so users can explicitly set their default sender
- Add 'Set as Primary' star button in identity manager modal
- Fix sendEmail() fallback identity resolution for local-part usernames
- Add i18n strings for all 8 supported locales

Fixes #43
This commit is contained in:
Linus Rath
2026-03-18 20:05:35 +01:00
parent b844b88733
commit fc79bf4f9b
12 changed files with 107 additions and 11 deletions
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identität erfolgreich erstellt",
"identity_updated": "Identität erfolgreich aktualisiert",
"identity_deleted": "Identität gelöscht",
"identity_set_primary": "Primäre Identität aktualisiert",
"identity_create_failed": "Identität erstellen fehlgeschlagen: {error}",
"identity_update_failed": "Identität aktualisieren fehlgeschlagen: {error}",
"identity_delete_failed": "Identität löschen fehlgeschlagen: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Diese Identität löschen? Dies kann nicht rückgängig gemacht werden.",
"cannot_delete": "Diese Identität kann nicht gelöscht werden",
"primary_identity": "Primär",
"set_as_primary": "Als primär festlegen",
"no_identities": "Keine Identitäten gefunden",
"display": {
"reply_to": "Antwort an:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identity created successfully",
"identity_updated": "Identity updated successfully",
"identity_deleted": "Identity deleted",
"identity_set_primary": "Primary identity updated",
"identity_create_failed": "Failed to create identity: {error}",
"identity_update_failed": "Failed to update identity: {error}",
"identity_delete_failed": "Failed to delete identity: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Delete this identity? This cannot be undone.",
"cannot_delete": "This identity cannot be deleted",
"primary_identity": "Primary",
"set_as_primary": "Set as primary",
"no_identities": "No identities found",
"display": {
"reply_to": "Reply-To:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identidad creada exitosamente",
"identity_updated": "Identidad actualizada exitosamente",
"identity_deleted": "Identidad eliminada",
"identity_set_primary": "Identidad principal actualizada",
"identity_create_failed": "Error al crear identidad: {error}",
"identity_update_failed": "Error al actualizar identidad: {error}",
"identity_delete_failed": "Error al eliminar identidad: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "¿Eliminar esta identidad? Esto no se puede deshacer.",
"cannot_delete": "Esta identidad no se puede eliminar",
"primary_identity": "Principal",
"set_as_primary": "Establecer como principal",
"no_identities": "No se encontraron identidades",
"display": {
"reply_to": "Responder a:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identité créée avec succès",
"identity_updated": "Identité mise à jour avec succès",
"identity_deleted": "Identité supprimée",
"identity_set_primary": "Identité principale mise à jour",
"identity_create_failed": "Échec de la création de l'identité: {error}",
"identity_update_failed": "Échec de la mise à jour de l'identité: {error}",
"identity_delete_failed": "Échec de la suppression de l'identité: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Supprimer cette identité ? Cette action est irréversible.",
"cannot_delete": "Cette identité ne peut pas être supprimée",
"primary_identity": "Principale",
"set_as_primary": "Définir comme principale",
"no_identities": "Aucune identité trouvée",
"display": {
"reply_to": "Répondre à :",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identità creata con successo",
"identity_updated": "Identità aggiornata con successo",
"identity_deleted": "Identità eliminata",
"identity_set_primary": "Identità principale aggiornata",
"identity_create_failed": "Impossibile creare l'identità: {error}",
"identity_update_failed": "Impossibile aggiornare l'identità: {error}",
"identity_delete_failed": "Impossibile eliminare l'identità: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Eliminare questa identità? Questa azione non può essere annullata.",
"cannot_delete": "Questa identità non può essere eliminata",
"primary_identity": "Principale",
"set_as_primary": "Imposta come principale",
"no_identities": "Nessuna identità trovata",
"display": {
"reply_to": "Rispondi a:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "送信者情報を作成しました",
"identity_updated": "送信者情報を更新しました",
"identity_deleted": "送信者情報を削除しました",
"identity_set_primary": "プライマリ送信者情報を更新しました",
"identity_create_failed": "送信者情報の作成に失敗しました: {error}",
"identity_update_failed": "送信者情報の更新に失敗しました: {error}",
"identity_delete_failed": "送信者情報の削除に失敗しました: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "この送信者情報を削除しますか?この操作は元に戻せません。",
"cannot_delete": "この送信者情報は削除できません",
"primary_identity": "プライマリ",
"set_as_primary": "プライマリに設定",
"no_identities": "送信者情報が見つかりません",
"display": {
"reply_to": "返信先:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identiteit succesvol aangemaakt",
"identity_updated": "Identiteit succesvol bijgewerkt",
"identity_deleted": "Identiteit verwijderd",
"identity_set_primary": "Primaire identiteit bijgewerkt",
"identity_create_failed": "Kan identiteit niet aanmaken: {error}",
"identity_update_failed": "Kan identiteit niet bijwerken: {error}",
"identity_delete_failed": "Kan identiteit niet verwijderen: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Deze identiteit verwijderen? Dit kan niet ongedaan worden gemaakt.",
"cannot_delete": "Deze identiteit kan niet worden verwijderd",
"primary_identity": "Primair",
"set_as_primary": "Instellen als primair",
"no_identities": "Geen identiteiten gevonden",
"display": {
"reply_to": "Antwoord naar:",
+2
View File
@@ -519,6 +519,7 @@
"identity_created": "Identidade criada com sucesso",
"identity_updated": "Identidade atualizada com sucesso",
"identity_deleted": "Identidade excluída",
"identity_set_primary": "Identidade principal atualizada",
"identity_create_failed": "Falha ao criar identidade: {error}",
"identity_update_failed": "Falha ao atualizar identidade: {error}",
"identity_delete_failed": "Falha ao excluir identidade: {error}",
@@ -1367,6 +1368,7 @@
"delete_confirm": "Excluir esta identidade? Isso não pode ser desfeito.",
"cannot_delete": "Esta identidade não pode ser excluída",
"primary_identity": "Principal",
"set_as_primary": "Definir como principal",
"no_identities": "Nenhuma identidade encontrada",
"display": {
"reply_to": "Responder para:",