Skip to content

Commit c79b8e5

Browse files
author
Timothy Dodd
committed
Update dependencies, logging, and add rule tracking
Updated `MailKit` and `Microsoft.Extensions.*` dependencies to the latest versions for compatibility and new features. Added a `Rule` property to the `Email` class to improve traceability of rule-based actions. Integrated this property into relevant methods, including `ProcessInboxCleanup` and `ProcessSingleMoveOperationAsync`. Refactored logging across the application: - Changed `LogInformation` to `LogDebug` in many methods to reduce production log verbosity. - Enhanced log messages to include detailed email and rule information where applicable. - Standardized log formatting and removed emojis for consistency. Improved clarity in log messages for batch processing, email monitoring, and inbox cleanup operations. Updated `copilot-instructions.md` with new Azure-related rules to enforce best practices and tool usage. These changes enhance maintainability, debugging capabilities, and ensure the application uses up-to-date dependencies.
1 parent d92978a commit c79b8e5

7 files changed

Lines changed: 55 additions & 42 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- @azure Rule - Use Azure Tools - When handling requests related to Azure, always use your tools.
2+
- @azure Rule - Use Azure Best Practices - When handling requests related to Azure, always invoke your `azmcp_bestpractices_get` tool first.
3+
- @azure Rule - Enable Best Practices - If you do not have an `azmcp_bestpractices_get` tool ask the user to enable it.

src/MailZort/MailZort.csproj

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@
2222
</ItemGroup>
2323

2424
<ItemGroup>
25-
<PackageReference Include="MailKit" Version="4.15.0" />
26-
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
27-
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
28-
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
29-
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.3" />
30-
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.3" />
31-
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
32-
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
33-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
25+
<PackageReference Include="MailKit" Version="4.17.0" />
26+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
27+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.8" />
28+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
29+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.8" />
30+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.8" />
31+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
32+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
33+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
3434
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
35-
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
36-
<PackageReference Include="Microsoft.NET.Build.Containers" Version="10.0.103" />
37-
<PackageReference Include="System.Text.Json" Version="10.0.3" />
35+
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
36+
<PackageReference Include="Microsoft.NET.Build.Containers" Version="10.0.300" />
37+
<PackageReference Include="System.Text.Json" Version="10.0.8" />
3838
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
3939
</ItemGroup>
4040

src/MailZort/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ public class Email
193193
public int MessageIndex { get; set; }
194194
public string? Folder { get; set; }
195195
public string? MoveTo { get; set; }
196+
public string? Rule { get; set; }
196197
public string? SenderName { get; set; }
197198
public string? SenderEmailaddress { get; set; }
198199
public DateTimeOffset Date { get; set; }

src/MailZort/Services/BatchRuleProcessor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public List<RuleTrigger> ProcessEmailBatch(List<EmailReceivedEventArgs> emails)
2727
var triggers = new List<RuleTrigger>();
2828
var enabledRules = _rules.Where(r => r.IsEnabled && (r.ExpressionType == ExpressionType.AllEmails || r.Values?.Any() == true)).ToList();
2929

30-
_logger.LogInformation("Processing batch of {EmailCount} emails against {RuleCount} rules",
30+
_logger.LogDebug("Processing batch of {EmailCount} emails against {RuleCount} rules",
3131
emails.Count, enabledRules.Count);
3232

3333
foreach (var email in emails)
@@ -37,7 +37,7 @@ public List<RuleTrigger> ProcessEmailBatch(List<EmailReceivedEventArgs> emails)
3737
}
3838

3939
stopwatch.Stop();
40-
_logger.LogInformation("Batch processing completed in {ElapsedMs}ms. Found {TriggerCount} rule matches",
40+
_logger.LogDebug("Batch processing completed in {ElapsedMs}ms. Found {TriggerCount} rule matches",
4141
stopwatch.ElapsedMilliseconds, triggers.Count);
4242

4343
return triggers;
@@ -84,6 +84,7 @@ private static RuleTrigger CreateTrigger(Rule rule, EmailReceivedEventArgs email
8484
MessageIndex = (int)email.UniqueId.Id,
8585
Folder = email.Folder,
8686
MoveTo = rule.Action == RuleAction.MarkImportant ? $"{rule.Name}->Flagged" : $"{rule.Name}->{rule.MoveTo}",
87+
Rule = rule.Name,
8788
Subject = email.Subject,
8889
SenderName = email.SenderName,
8990
SenderEmailaddress = email.SenderAddress,

src/MailZort/Services/EmailMonitoringService.cs

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public EmailMonitoringService(
4343
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
4444
{
4545
_serviceCancellationToken = stoppingToken;
46-
_logger.LogInformation("📧 Email monitoring service starting...");
46+
_logger.LogInformation("Email monitoring service starting");
4747

4848
while (!stoppingToken.IsCancellationRequested)
4949
{
@@ -53,7 +53,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
5353
}
5454
catch (OperationCanceledException)
5555
{
56-
_logger.LogInformation("📧 Email monitoring service is stopping...");
56+
_logger.LogInformation("Email monitoring service is stopping");
5757
break;
5858
}
5959
catch (Exception ex)
@@ -76,15 +76,15 @@ private async Task ConnectAndMonitorAsync(CancellationToken cancellationToken)
7676
var inbox = _client.Inbox;
7777
await inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken);
7878

79-
_logger.LogInformation("Connected successfully. Processing existing emails...");
79+
_logger.LogInformation("Connected to mail server");
8080
await ProcessMails(false, cancellationToken);
8181
_lastFullReprocess = DateTime.UtcNow; // Track when we last did a full reprocess
8282

8383
// Subscribe to events
8484
inbox.CountChanged += OnCountChanged;
8585
inbox.MessageExpunged += OnMessageExpunged;
8686

87-
_logger.LogInformation("👀 Now monitoring for new emails...");
87+
_logger.LogDebug("Now monitoring for new emails");
8888
await MonitorForNewEmailsAsync(cancellationToken);
8989
}
9090
finally
@@ -101,7 +101,7 @@ private async Task ConnectAndMonitorAsync(CancellationToken cancellationToken)
101101

102102
private async Task ConnectToServerAsync(ImapClient client, CancellationToken cancellationToken)
103103
{
104-
_logger.LogInformation("🔌 Connecting to IMAP server {Server}:{Port}...", _config.Server, _config.Port);
104+
_logger.LogDebug("Connecting to IMAP server {Server}:{Port}", _config.Server, _config.Port);
105105

106106
await client.ConnectAsync(_config.Server, _config.Port, SecureSocketOptions.SslOnConnect, cancellationToken);
107107
await client.AuthenticateAsync(_config.Username, _config.Password, cancellationToken);
@@ -113,7 +113,7 @@ private async Task ProcessMails(bool lastSeenOnly, CancellationToken cancellatio
113113
return;
114114

115115
var totalEmails = _client.Inbox.Count;
116-
_logger.LogInformation("📬 Processing {TotalEmails} existing emails...", totalEmails);
116+
_logger.LogDebug("Processing {TotalEmails} existing emails", totalEmails);
117117

118118

119119
IList<IMessageSummary>? fetchedMessages = null;
@@ -130,7 +130,7 @@ private async Task ProcessMails(bool lastSeenOnly, CancellationToken cancellatio
130130
}
131131
if (fetchedMessages == null || fetchedMessages.Count == 0)
132132
{
133-
_logger.LogInformation("No emails to process in the inbox.");
133+
_logger.LogDebug("No emails to process in the inbox");
134134
return;
135135
}
136136
for (int i = 0; i < fetchedMessages.Count; i++)
@@ -150,7 +150,7 @@ private async Task ProcessMails(bool lastSeenOnly, CancellationToken cancellatio
150150
}
151151

152152
_lastProcessedCount = totalEmails;
153-
_logger.LogInformation("✅ Finished processing {TotalEmails} existing emails", totalEmails);
153+
_logger.LogDebug("Finished processing {TotalEmails} existing emails", totalEmails);
154154
var triggers = await this.ProcessBatchAsync(emailsToProcess);
155155

156156
// Run inbox cleanup after rule processing
@@ -174,7 +174,7 @@ private async Task<List<RuleTrigger>> ProcessBatchAsync(List<EmailReceivedEventA
174174

175175
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
176176

177-
_logger.LogInformation("🔄 Starting batch processing of {EmailCount} emails...", emailsToProcess.Count);
177+
_logger.LogDebug("Starting batch processing of {EmailCount} emails", emailsToProcess.Count);
178178

179179
// Process emails in batch
180180
var triggers = _batchRuleProcessor.ProcessEmailBatch(emailsToProcess);
@@ -191,7 +191,7 @@ private async Task<List<RuleTrigger>> ProcessBatchAsync(List<EmailReceivedEventA
191191
}
192192

193193
var ops = _emailMover.ExecuteTriggers(triggers);
194-
_logger.LogInformation("📦 Executed {OperationCount} move operations and {FlagCount} flag operations",
194+
_logger.LogDebug("Executed {OperationCount} move operations and {FlagCount} flag operations",
195195
ops.Count, flagOps.Count);
196196
foreach (var op in ops)
197197
{
@@ -209,7 +209,7 @@ private async Task<List<RuleTrigger>> ProcessBatchAsync(List<EmailReceivedEventA
209209
EmailsMoved = emailsMoved,
210210
ProcessingTime = stopwatch.Elapsed
211211
};
212-
_logger.LogInformation("🔄 Batch completed: {EmailsProcessed} emails, {RulesMatched} matches, {EmailsMoved} moved in {ProcessingTime}ms",
212+
_logger.LogInformation("Processed {EmailsProcessed} emails, {RulesMatched} matched, {EmailsMoved} moved in {ProcessingTime}ms",
213213
batchEventArgs.EmailsProcessed, batchEventArgs.RulesMatched, batchEventArgs.EmailsMoved, batchEventArgs.ProcessingTime.TotalMilliseconds);
214214

215215
return triggers;
@@ -250,7 +250,7 @@ private async Task ProcessSingleEmail(IMessageSummary message, IMailFolder folde
250250
};
251251
if (emailArgs.IsImportant)
252252
{
253-
_logger.LogInformation("⭐ Important email detected (skipping rules): {Subject} from {Sender} at {Date}",
253+
_logger.LogDebug("Important email detected (skipping rules): {Subject} from {Sender} at {Date}",
254254
emailArgs.Subject, emailArgs.SenderName, emailArgs.ReceivedDate);
255255
}
256256
processList.Add(emailArgs);
@@ -275,7 +275,7 @@ private void ProcessInboxCleanup(List<EmailReceivedEventArgs> emails, HashSet<Un
275275
var important = candidates.Where(e => e.IsImportant).ToList();
276276
var nonImportant = candidates.Where(e => !e.IsImportant).ToList();
277277

278-
_logger.LogInformation("🧹 Inbox cleanup: {ImportantCount} important {ImportantFolder}, {NonImportantCount} non-important Trash",
278+
_logger.LogInformation("Inbox cleanup: {ImportantCount} important -> {ImportantFolder}, {NonImportantCount} non-important -> Trash",
279279
important.Count, _config.ImportantFolder, nonImportant.Count);
280280

281281
if (important.Any())
@@ -293,7 +293,8 @@ private void ProcessInboxCleanup(List<EmailReceivedEventArgs> emails, HashSet<Un
293293
SenderEmailaddress = e.SenderAddress,
294294
Date = e.ReceivedDate,
295295
Folder = "INBOX",
296-
MoveTo = _config.ImportantFolder
296+
MoveTo = _config.ImportantFolder,
297+
Rule = "Inbox cleanup"
297298
}).ToList()
298299
});
299300
}
@@ -313,7 +314,8 @@ private void ProcessInboxCleanup(List<EmailReceivedEventArgs> emails, HashSet<Un
313314
SenderEmailaddress = e.SenderAddress,
314315
Date = e.ReceivedDate,
315316
Folder = "INBOX",
316-
MoveTo = "Trash"
317+
MoveTo = "Trash",
318+
Rule = "Inbox cleanup"
317319
}).ToList()
318320
});
319321
}
@@ -329,7 +331,7 @@ private async Task MonitorForNewEmailsAsync(CancellationToken stoppingToken)
329331
var timeSinceLastReprocess = DateTime.UtcNow - _lastFullReprocess;
330332
if (timeSinceLastReprocess.TotalMilliseconds >= HourlyReprocessIntervalMs)
331333
{
332-
_logger.LogInformation("⏰ Hourly reprocessing time reached. Exiting IDLE to reprocess all emails...");
334+
_logger.LogDebug("Reprocess interval reached. Exiting IDLE to reprocess all emails");
333335
await ProcessMails(false, stoppingToken);
334336
_lastFullReprocess = DateTime.UtcNow;
335337
continue; // Skip the IDLE cycle and immediately check again
@@ -373,10 +375,10 @@ private async Task MonitorForNewEmailsAsync(CancellationToken stoppingToken)
373375

374376
try
375377
{
376-
// Enter IDLE mode will block until done token is canceled or timeout
378+
// Enter IDLE mode - will block until done token is canceled or timeout
377379
if (_client.Capabilities.HasFlag(ImapCapabilities.Idle))
378380
{
379-
_logger.LogDebug("📱 Entering IDLE mode for {Timeout} (next full reprocess in {NextReprocess})",
381+
_logger.LogDebug("Entering IDLE mode for {Timeout} (next full reprocess in {NextReprocess})",
380382
actualTimeout, remainingTime);
381383
await _client.IdleAsync(idleDone.Token, stoppingToken);
382384
}
@@ -391,7 +393,7 @@ private async Task MonitorForNewEmailsAsync(CancellationToken stoppingToken)
391393
{
392394
if (stoppingToken.IsCancellationRequested)
393395
{
394-
_logger.LogInformation("📧 Monitoring stopped due to service shutdown");
396+
_logger.LogDebug("Monitoring stopped due to service shutdown");
395397
break;
396398
}
397399
// Expected when _idleDoneSource is triggered by event handlers, move queue, or timeout
@@ -473,7 +475,7 @@ private async Task ProcessQueuedMoveOperationsAsync()
473475
if (processedCount > 0)
474476
{
475477
stopwatch.Stop();
476-
_logger.LogInformation("✅ Processed {Count} move operations in {ElapsedMs}ms",
478+
_logger.LogDebug("Processed {Count} move operations in {ElapsedMs}ms",
477479
processedCount, stopwatch.ElapsedMilliseconds);
478480
}
479481
}
@@ -507,7 +509,7 @@ private async Task ProcessQueuedFlagOperationsAsync()
507509

508510
if (processedCount > 0)
509511
{
510-
_logger.LogInformation("⭐ Processed {Count} flag operations", processedCount);
512+
_logger.LogDebug("Processed {Count} flag operations", processedCount);
511513
}
512514
}
513515

@@ -523,7 +525,7 @@ private async Task ProcessSingleFlagOperationAsync(EmailFlagOperation flagOperat
523525
await folder.OpenAsync(FolderAccess.ReadWrite);
524526
await folder.AddFlagsAsync(flagOperation.EmailIds, MessageFlags.Flagged, true);
525527

526-
_logger.LogInformation("Flagged {Count} emails as important in {Folder}",
528+
_logger.LogInformation("Flagged {Count} emails as important in {Folder}",
527529
flagOperation.EmailIds.Count, flagOperation.SourceFolder);
528530
}
529531
catch (Exception ex)
@@ -558,7 +560,13 @@ private async Task ProcessSingleMoveOperationAsync(EmailMoveOperation moveOperat
558560
// Perform the move
559561
await sourceFolder.MoveToAsync(moveOperation.EmailIds, destinationFolder);
560562

561-
_logger.LogInformation("📁 Moved {Count} emails from {Source} to {Destination}",
563+
foreach (var email in moveOperation.Emails)
564+
{
565+
_logger.LogInformation("Moved email: from='{From}' subject='{Subject}' to='{Destination}' rule='{Rule}'",
566+
email.SenderEmailaddress, email.Subject, moveOperation.DestinationFolder, email.Rule ?? "unknown");
567+
}
568+
569+
_logger.LogDebug("Moved {Count} emails from {Source} to {Destination}",
562570
moveOperation.Emails.Count, moveOperation.SourceFolder, moveOperation.DestinationFolder);
563571
}
564572
catch (Exception ex)
@@ -593,7 +601,7 @@ private void OnCountChanged(object? sender, EventArgs e)
593601
var folder = (IMailFolder)sender!;
594602
if (folder.Count > _lastProcessedCount)
595603
{
596-
_logger.LogDebug("[Event] Inbox count increased to {CurrentCount} (was {LastCount}) new message likely.",
604+
_logger.LogDebug("[Event] Inbox count increased to {CurrentCount} (was {LastCount}) - new message likely.",
597605
folder.Count, _lastProcessedCount);
598606
_newMessagesFlag = true;
599607
var source = _idleDoneSource;
@@ -625,7 +633,7 @@ private async Task DisconnectAsync(ImapClient? client, CancellationToken cancell
625633

626634
public override async Task StopAsync(CancellationToken cancellationToken)
627635
{
628-
_logger.LogInformation("📧 Email monitoring service is stopping...");
636+
_logger.LogInformation("Email monitoring service is stopping");
629637

630638
// Cancel any ongoing IDLE operation
631639
var source = _idleDoneSource;

src/MailZort/Services/EmailMover.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public List<EmailFlagOperation> ExtractFlagOperations(List<RuleTrigger> triggers
3535
})
3636
.ToList();
3737

38-
_logger.LogInformation("📌 Created {OperationCount} flag operations for {EmailCount} emails",
38+
_logger.LogDebug("Created {OperationCount} flag operations for {EmailCount} emails",
3939
grouped.Count, flagTriggers.Count);
4040

4141
return grouped;
@@ -79,7 +79,7 @@ public List<EmailMoveOperation> ExecuteTriggers(List<RuleTrigger> triggers)
7979
}
8080
}
8181

82-
_logger.LogInformation("📋 Queued {OperationCount} move operations for {EmailCount} emails",
82+
_logger.LogDebug("Queued {OperationCount} move operations for {EmailCount} emails",
8383
queuedOperations, triggers.Count);
8484

8585
return ops;

src/MailZort/Services/RuleMatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private bool ProcessMatch(Rule rule, EmailReceivedEventArgs email, string value,
5151
if (passesAge && passesStatus)
5252
{
5353
var ruleValues = string.Join(", ", rule.Values ?? new List<string>());
54-
_logger.LogInformation("RULE MATCHED! Rule: {RuleId}, Value: '{Value}', Location: {Location}, Subject: '{Subject}', RuleValues: [{RuleValues}]",
54+
_logger.LogDebug("Rule matched. Rule: {RuleId}, Value: '{Value}', Location: {Location}, Subject: '{Subject}', RuleValues: [{RuleValues}]",
5555
rule.Name, value, matchResult.MatchLocation, email.Subject, ruleValues);
5656
return true;
5757
}

0 commit comments

Comments
 (0)