Skip to content

Commit a7a7171

Browse files
author
Timothy Dodd
committed
Add support for marking emails as important
Introduced a `RuleAction` enum to support `MarkImportant` and `Move` actions for email rules. Added the `EmailFlagOperation` class and `_flagQueue` in `EmailMonitoringService` to manage flagging operations separately from move operations. Updated the `Rule` and `RuleTrigger` classes to include an `Action` property. Modified `BatchRuleProcessor` to handle `MarkImportant` actions. Added methods in `EmailMonitoringService` to process queued flag operations and flag emails as important. Enhanced `IEmailMover` with an `ExtractFlagOperations` method to group and queue flagging operations. Updated `ExecuteTriggers` to process only `Move` actions for move operations. Updated `appsettings.json` to include a new rule for marking emails from specific senders as important and changed `StoreMovedMessages` to `false`. These changes add the ability to flag emails as important while maintaining existing move functionality.
1 parent 0b3581b commit a7a7171

5 files changed

Lines changed: 136 additions & 6 deletions

File tree

src/MailZort/Program.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public class Rule
8989
/// If true, rule only matches emails that are NOT marked as important. If false or null, importance is ignored.
9090
/// </summary>
9191
public bool? RequireNotImportant { get; set; }
92+
public RuleAction Action { get; set; } = RuleAction.Move;
9293
}
9394
public class EmailSettings
9495
{
@@ -110,6 +111,7 @@ public class RuleTrigger
110111
public required string To { get; set; }
111112
public required UniqueId Id { get; set; }
112113
public required Email Email { get; set; }
114+
public RuleAction Action { get; set; } = RuleAction.Move;
113115
}
114116
public enum LookIn
115117
{
@@ -154,3 +156,16 @@ public class BatchProcessingEventArgs : EventArgs
154156
public int EmailsMoved { get; set; }
155157
public TimeSpan ProcessingTime { get; set; }
156158
}
159+
160+
public enum RuleAction
161+
{
162+
Move,
163+
MarkImportant
164+
}
165+
166+
public class EmailFlagOperation
167+
{
168+
public string SourceFolder { get; set; } = string.Empty;
169+
public List<UniqueId> EmailIds { get; set; } = new();
170+
public DateTime QueuedAt { get; set; } = DateTime.UtcNow;
171+
}

src/MailZort/Services/BatchRuleProcessor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,13 @@ private static RuleTrigger CreateTrigger(Rule rule, EmailReceivedEventArgs email
9999
{
100100
Id = email.UniqueId,
101101
From = email.Folder,
102-
To = rule.MoveTo,
102+
To = rule.Action == RuleAction.MarkImportant ? string.Empty : rule.MoveTo,
103+
Action = rule.Action,
103104
Email = new Email
104105
{
105106
MessageIndex = (int)email.UniqueId.Id,
106107
Folder = email.Folder,
107-
MoveTo = $"{rule.Name}->{rule.MoveTo}",
108+
MoveTo = rule.Action == RuleAction.MarkImportant ? $"{rule.Name}->Flagged" : $"{rule.Name}->{rule.MoveTo}",
108109
Subject = email.Subject,
109110
SenderName = email.SenderName,
110111
SenderEmailaddress = email.SenderAddress,

src/MailZort/Services/EmailMonitoringService.cs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class EmailMonitoringService : BackgroundService
2727
private readonly IEmailMover _emailMover;
2828
private CancellationToken _serviceCancellationToken;
2929
private readonly ConcurrentQueue<EmailMoveOperation> _moveQueue = new();
30+
private readonly ConcurrentQueue<EmailFlagOperation> _flagQueue = new();
3031
private DateTime _lastFullReprocess = DateTime.MinValue;
3132

3233
public EmailMonitoringService(
@@ -187,15 +188,22 @@ private async Task<List<RuleTrigger>> ProcessBatchAsync(List<EmailReceivedEventA
187188
var emailsMoved = 0;
188189
if (triggers.Any())
189190
{
191+
// Extract and queue flag operations
192+
var flagOps = _emailMover.ExtractFlagOperations(triggers);
193+
foreach (var flagOp in flagOps)
194+
{
195+
_flagQueue.Enqueue(flagOp);
196+
}
197+
190198
var ops = _emailMover.ExecuteTriggers(triggers);
191-
_logger.LogInformation("📦 Executed {OperationCount} email move operations", ops.Count);
192-
// Count how many emails were moved
199+
_logger.LogInformation("📦 Executed {OperationCount} move operations and {FlagCount} flag operations",
200+
ops.Count, flagOps.Count);
193201
foreach (var op in ops)
194202
{
195203
this._moveQueue.Enqueue(op);
196204
}
197205

198-
emailsMoved = triggers.Count;
206+
emailsMoved = triggers.Count(t => t.Action == RuleAction.Move);
199207
}
200208

201209
stopwatch.Stop();
@@ -332,6 +340,12 @@ private async Task MonitorForNewEmailsAsync(CancellationToken stoppingToken)
332340
continue; // Skip the IDLE cycle and immediately check again
333341
}
334342

343+
// Process any queued flag operations
344+
if (_flagQueue.Count > 0)
345+
{
346+
await ProcessQueuedFlagOperationsAsync();
347+
}
348+
335349
// Process any queued move operations
336350
if (_moveQueue.Count > 0)
337351
{
@@ -469,6 +483,68 @@ private async Task ProcessQueuedMoveOperationsAsync()
469483
}
470484
}
471485

486+
private async Task ProcessQueuedFlagOperationsAsync()
487+
{
488+
if (_client == null || !_client.IsConnected)
489+
{
490+
_logger.LogWarning("Cannot process flag operations: client not connected");
491+
return;
492+
}
493+
494+
var processedCount = 0;
495+
496+
while (_flagQueue.TryDequeue(out var flagOperation) && flagOperation != null)
497+
{
498+
try
499+
{
500+
await ProcessSingleFlagOperationAsync(flagOperation);
501+
processedCount++;
502+
}
503+
catch (Exception ex)
504+
{
505+
_logger.LogError(ex, "Error processing flag operation for folder {Source}",
506+
flagOperation.SourceFolder);
507+
}
508+
509+
if (_serviceCancellationToken.IsCancellationRequested)
510+
break;
511+
}
512+
513+
if (processedCount > 0)
514+
{
515+
_logger.LogInformation("⭐ Processed {Count} flag operations", processedCount);
516+
}
517+
}
518+
519+
private async Task ProcessSingleFlagOperationAsync(EmailFlagOperation flagOperation)
520+
{
521+
if (!flagOperation.EmailIds.Any())
522+
return;
523+
524+
IMailFolder? folder = null;
525+
try
526+
{
527+
folder = _client!.GetFolder(flagOperation.SourceFolder);
528+
await folder.OpenAsync(FolderAccess.ReadWrite);
529+
await folder.AddFlagsAsync(flagOperation.EmailIds, MessageFlags.Flagged, true);
530+
531+
_logger.LogInformation("⭐ Flagged {Count} emails as important in {Folder}",
532+
flagOperation.EmailIds.Count, flagOperation.SourceFolder);
533+
}
534+
catch (Exception ex)
535+
{
536+
_logger.LogError(ex, "Error flagging emails in {Folder}", flagOperation.SourceFolder);
537+
throw;
538+
}
539+
finally
540+
{
541+
if (folder is { IsOpen: true })
542+
{
543+
await folder.CloseAsync();
544+
}
545+
}
546+
}
547+
472548
private async Task ProcessSingleMoveOperationAsync(EmailMoveOperation moveOperation)
473549
{
474550
if (!moveOperation.Emails.Any())

src/MailZort/Services/EmailMover.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace MailZort.Services;
77
public interface IEmailMover
88
{
99
List<EmailMoveOperation> ExecuteTriggers(List<RuleTrigger> triggers);
10+
List<EmailFlagOperation> ExtractFlagOperations(List<RuleTrigger> triggers);
1011
}
1112

1213
public class EmailMover : IEmailMover
@@ -19,6 +20,27 @@ public EmailMover(ILogger<EmailMover> logger)
1920

2021
}
2122

23+
public List<EmailFlagOperation> ExtractFlagOperations(List<RuleTrigger> triggers)
24+
{
25+
var flagTriggers = triggers.Where(t => t.Action == RuleAction.MarkImportant).ToList();
26+
if (!flagTriggers.Any())
27+
return new List<EmailFlagOperation>();
28+
29+
var grouped = flagTriggers
30+
.GroupBy(t => t.From)
31+
.Select(g => new EmailFlagOperation
32+
{
33+
SourceFolder = g.Key,
34+
EmailIds = g.Select(t => t.Id).ToList()
35+
})
36+
.ToList();
37+
38+
_logger.LogInformation("📌 Created {OperationCount} flag operations for {EmailCount} emails",
39+
grouped.Count, flagTriggers.Count);
40+
41+
return grouped;
42+
}
43+
2244
public List<EmailMoveOperation> ExecuteTriggers(List<RuleTrigger> triggers)
2345
{
2446
List<EmailMoveOperation> ops = new List<EmailMoveOperation>();
@@ -28,6 +50,11 @@ public List<EmailMoveOperation> ExecuteTriggers(List<RuleTrigger> triggers)
2850
return ops;
2951
}
3052

53+
// Only process Move triggers for move operations
54+
triggers = triggers.Where(t => t.Action == RuleAction.Move).ToList();
55+
if (!triggers.Any())
56+
return ops;
57+
3158
var groupedTriggers = GroupTriggersByFolder(triggers);
3259
var queuedOperations = 0;
3360

src/MailZort/appsettings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"EmailSettings": {
33
"Trash": "Deleted Messages",
4-
"StoreMovedMessages": true
4+
"StoreMovedMessages": false
55
},
66
"rules": [
77
{
@@ -82,6 +82,17 @@
8282
"(?:save\\s+(on|big|up|\\$))"
8383
]
8484
},
85+
{
86+
"Name": "ImportantSenders",
87+
"Folder": "Inbox",
88+
"Action": 1,
89+
"LookIn": 5,
90+
"ExpressionType": 0,
91+
"Values": [
92+
"boss@example.com",
93+
"cto@example.com"
94+
]
95+
},
8596
{
8697
"Name": "CleanupOldUnread",
8798
"Folder": "Inbox",

0 commit comments

Comments
 (0)