Improve alias capability model and Outlook alias sync

This commit is contained in:
Burak Kaan Köse
2026-04-13 01:09:40 +02:00
parent 6fd66810e9
commit 40b15b4f08
18 changed files with 444 additions and 45 deletions
+32 -6
View File
@@ -250,7 +250,9 @@ public class AccountService : BaseDatabaseService, IAccountService
IsRootAlias = true,
IsVerified = true,
ReplyToAddress = address,
Id = Guid.NewGuid()
Id = Guid.NewGuid(),
Source = AliasSource.Manual,
SendCapability = AliasSendCapability.Confirmed
};
await Connection.InsertAsync(rootAlias, typeof(MailAccountAlias)).ConfigureAwait(false);
@@ -261,7 +263,7 @@ public class AccountService : BaseDatabaseService, IAccountService
public async Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId)
{
return await Connection.QueryAsync<MailAccountAlias>(
"SELECT * FROM MailAccountAlias WHERE AccountId = ? ORDER BY IsRootAlias DESC",
"SELECT * FROM MailAccountAlias WHERE AccountId = ? ORDER BY IsRootAlias DESC, IsPrimary DESC, AliasAddress ASC",
accountId).ConfigureAwait(false);
}
@@ -491,11 +493,16 @@ public class AccountService : BaseDatabaseService, IAccountService
public async Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases)
{
var localAliases = await GetAccountAliasesAsync(account.Id).ConfigureAwait(false);
var rootAlias = localAliases.Find(a => a.IsRootAlias);
var normalizedRemoteAliases = remoteAccountAliases ?? [];
foreach (var remoteAlias in remoteAccountAliases)
foreach (var remoteAlias in normalizedRemoteAliases)
{
var existingAlias = localAliases.Find(a => a.AccountId == account.Id && a.AliasAddress == remoteAlias.AliasAddress);
if (string.IsNullOrWhiteSpace(remoteAlias?.AliasAddress))
continue;
var existingAlias = localAliases.Find(a =>
a.AccountId == account.Id &&
a.AliasAddress.Equals(remoteAlias.AliasAddress, StringComparison.OrdinalIgnoreCase));
if (existingAlias == null)
{
@@ -509,7 +516,9 @@ public class AccountService : BaseDatabaseService, IAccountService
ReplyToAddress = remoteAlias.ReplyToAddress,
Id = Guid.NewGuid(),
IsRootAlias = remoteAlias.IsRootAlias,
AliasSenderName = remoteAlias.AliasSenderName
AliasSenderName = remoteAlias.AliasSenderName,
Source = remoteAlias.Source,
SendCapability = remoteAlias.SendCapability
};
await Connection.InsertAsync(newAlias, typeof(MailAccountAlias));
@@ -522,6 +531,8 @@ public class AccountService : BaseDatabaseService, IAccountService
existingAlias.IsVerified = remoteAlias.IsVerified;
existingAlias.ReplyToAddress = remoteAlias.ReplyToAddress;
existingAlias.AliasSenderName = remoteAlias.AliasSenderName;
existingAlias.Source = remoteAlias.Source;
existingAlias.SendCapability = remoteAlias.SendCapability;
await Connection.UpdateAsync(existingAlias, typeof(MailAccountAlias));
}
@@ -553,6 +564,21 @@ public class AccountService : BaseDatabaseService, IAccountService
}
}
public async Task UpdateAliasSendCapabilityAsync(Guid accountId, string aliasAddress, AliasSendCapability capability)
{
if (string.IsNullOrWhiteSpace(aliasAddress))
return;
var aliases = await GetAccountAliasesAsync(accountId).ConfigureAwait(false);
var alias = aliases.FirstOrDefault(a => a.AliasAddress.Equals(aliasAddress, StringComparison.OrdinalIgnoreCase));
if (alias == null)
return;
alias.SendCapability = capability;
await Connection.UpdateAsync(alias, typeof(MailAccountAlias)).ConfigureAwait(false);
}
public async Task DeleteAccountAliasAsync(Guid aliasId)
{
// Create query to delete alias.
+67 -6
View File
@@ -56,7 +56,8 @@ public class MailService : BaseDatabaseService, IMailService
public async Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
{
var composerAccount = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions);
var selectedAlias = await ResolveDraftAliasAsync(composerAccount, draftCreationOptions).ConfigureAwait(false);
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions, selectedAlias).ConfigureAwait(false);
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
@@ -74,8 +75,8 @@ public class MailService : BaseDatabaseService, IMailService
UniqueId = Guid.Parse(mimeUniqueId),
Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id.
CreationDate = DateTime.UtcNow,
FromAddress = primaryAlias?.AliasAddress ?? composerAccount.Address,
FromName = composerAccount.SenderName,
FromAddress = selectedAlias?.AliasAddress ?? primaryAlias?.AliasAddress ?? composerAccount.Address,
FromName = selectedAlias?.AliasSenderName ?? composerAccount.SenderName,
HasAttachments = false,
Importance = MailImportance.Normal,
Subject = createdDraftMimeMessage.Subject,
@@ -1016,7 +1017,7 @@ public class MailService : BaseDatabaseService, IMailService
await _contactService.SaveAddressInformationAsync(contacts).ConfigureAwait(false);
}
private async Task<MimeMessage> CreateDraftMimeAsync(MailAccount account, DraftCreationOptions draftCreationOptions)
private async Task<MimeMessage> CreateDraftMimeAsync(MailAccount account, DraftCreationOptions draftCreationOptions, MailAccountAlias selectedAlias)
{
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
// Same unique id will be used for the local copy as well.
@@ -1028,10 +1029,15 @@ public class MailService : BaseDatabaseService, IMailService
};
EnsureOutgoingMessageId(message);
var primaryAlias = await _accountService.GetPrimaryAccountAliasAsync(account.Id) ?? throw new MissingAliasException();
selectedAlias ??= await _accountService.GetPrimaryAccountAliasAsync(account.Id) ?? throw new MissingAliasException();
// Set FromName and FromAddress by alias.
message.From.Add(new MailboxAddress(account.SenderName, primaryAlias.AliasAddress));
message.From.Add(new MailboxAddress(selectedAlias.AliasSenderName ?? account.SenderName, selectedAlias.AliasAddress));
if (!string.IsNullOrWhiteSpace(selectedAlias.ReplyToAddress))
{
message.ReplyTo.Add(new MailboxAddress(selectedAlias.ReplyToAddress, selectedAlias.ReplyToAddress));
}
var builder = new BodyBuilder();
@@ -1052,6 +1058,61 @@ public class MailService : BaseDatabaseService, IMailService
return message;
}
private async Task<MailAccountAlias> ResolveDraftAliasAsync(MailAccount account, DraftCreationOptions draftCreationOptions)
{
var aliases = await _accountService.GetAccountAliasesAsync(account.Id).ConfigureAwait(false);
var primaryAlias = aliases.FirstOrDefault(a => a.IsPrimary) ?? aliases.FirstOrDefault();
if (draftCreationOptions?.ReferencedMessage?.MimeMessage == null)
return primaryAlias;
var referencedMessage = draftCreationOptions.ReferencedMessage.MimeMessage;
MailAccountAlias FindAlias(string address)
{
if (string.IsNullOrWhiteSpace(address))
return null;
return aliases.FirstOrDefault(a => a.AliasAddress.Equals(address.Trim(), StringComparison.OrdinalIgnoreCase));
}
var deliveredToAlias = FindAlias(ExtractAddressFromHeader(referencedMessage.Headers["Delivered-To"]))
?? FindAlias(ExtractAddressFromHeader(referencedMessage.Headers["X-Original-To"]));
if (deliveredToAlias != null)
return deliveredToAlias;
foreach (var mailbox in referencedMessage.To.Mailboxes)
{
var matchedAlias = FindAlias(mailbox.Address);
if (matchedAlias != null)
return matchedAlias;
}
foreach (var mailbox in referencedMessage.Cc.Mailboxes)
{
var matchedAlias = FindAlias(mailbox.Address);
if (matchedAlias != null)
return matchedAlias;
}
return primaryAlias;
}
private static string ExtractAddressFromHeader(string headerValue)
{
if (string.IsNullOrWhiteSpace(headerValue))
return string.Empty;
var trimmed = headerValue.Trim();
var leftBracketIndex = trimmed.LastIndexOf('<');
var rightBracketIndex = trimmed.LastIndexOf('>');
if (leftBracketIndex >= 0 && rightBracketIndex > leftBracketIndex)
return trimmed[(leftBracketIndex + 1)..rightBracketIndex].Trim();
return trimmed.Trim().Trim('<', '>', '"', '\'');
}
private string CreateHtmlGap()
{
var template = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px"><br></div>""";