From 93d3be490901f49fa6441482ec01b33413d15a20 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Thu, 16 Jul 2026 16:07:00 +0200 Subject: [PATCH 1/7] [SOA] Bound outbound reply retries Stop retrying failed outbound replies after five attempts and allow users to reset exhausted retries from the message page. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Integration/SOAEmailMessage.Page.al | 32 +++++ .../src/Integration/SOAReplyAttempt.Table.al | 40 ++++++ .../Integration/SOAReplyRetryMgt.Codeunit.al | 131 ++++++++++++++++++ .../Integration/SOASendReplies.Codeunit.al | 98 ++++++------- .../Permissions/SOAObjects.PermissionSet.al | 1 + 5 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al create mode 100644 src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al index 20a8e9ece20..a6290d345bb 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al @@ -244,6 +244,33 @@ page 4404 "SOA Email Message" } } + actions + { + area(Processing) + { + action(RetrySending) + { + ApplicationArea = All; + Caption = 'Retry sending'; + Image = Refresh; + ToolTip = 'Reset the failed sending attempts so the reply is retried during the next agent run.'; + Visible = RetrySendingVisible; + + trigger OnAction() + var + SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; + begin + if not Confirm(RetrySendingQst) then + exit; + + SOAReplyRetryMgt.ResetAttempts(Rec."Task ID", Rec.ID); + Message(RetrySendingScheduledMsg); + UpdateControls(); + end; + } + } + } + trigger OnAfterGetRecord() begin UpdateControls(); @@ -256,11 +283,13 @@ page 4404 "SOA Email Message" local procedure UpdateControls() var + SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; EmailAddress: Text; begin UpdatePageCaption(); UpdateEmailFields(EmailAddress); UpdateContactInformation(EmailAddress); + RetrySendingVisible := (Rec.Type = Rec.Type::Output) and (Rec.Status = Rec.Status::Reviewed) and SOAReplyRetryMgt.IsExhausted(Rec."Task ID", Rec.ID); CurrPage.Attachments.Page.LoadRecords(Rec); end; @@ -422,8 +451,11 @@ page 4404 "SOA Email Message" ShowAttachmentTxt: Text; AttachmentsVisible: Boolean; BlockedStatusVisible: Boolean; + RetrySendingVisible: Boolean; OutgoingMessageTxt: Label 'Outgoing email'; IncomingMessageTxt: Label 'Incoming email'; SelectContactOrCreateLbl: Label 'Select an existing contact, or create a new one'; ShowAttachmentLbl: Label 'Show attachments (%1)', Comment = '%1 = Attachment count'; + RetrySendingQst: Label 'Do you want to retry sending this reply?'; + RetrySendingScheduledMsg: Label 'The reply will be retried during the next agent run.'; } \ No newline at end of file diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al new file mode 100644 index 00000000000..3341b4b4b37 --- /dev/null +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +#pragma warning disable AS0007 +namespace Microsoft.Agent.SalesOrderAgent; + +using System.Agents; + +table 4589 "SOA Reply Attempt" +{ + Access = Internal; + DataClassification = SystemMetadata; + InherentEntitlements = RIMDX; + InherentPermissions = RIMDX; + ReplicateData = false; + + fields + { + field(1; "Task ID"; BigInteger) + { + TableRelation = "Agent Task".ID; + } + field(2; "Message ID"; Guid) + { + } + field(3; "Attempt Count"; Integer) + { + } + } + + keys + { + key(Key1; "Task ID", "Message ID") + { + Clustered = true; + } + } +} diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al new file mode 100644 index 00000000000..2f4f5b85151 --- /dev/null +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al @@ -0,0 +1,131 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +#pragma warning disable AS0007 +namespace Microsoft.Agent.SalesOrderAgent; + +using System.Agents; +using System.Email; +using System.Telemetry; + +codeunit 4580 "SOA Reply Retry Mgt." +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + Permissions = tabledata "SOA Reply Attempt" = RIMD; + TableNo = "Agent Task Message"; + + trigger OnRun() + var + InputAgentTaskMessage: Record "Agent Task Message"; + SOASetup: Record "SOA Setup"; + begin + InputAgentTaskMessage.Get(Rec."Task ID", Rec."Input Message ID"); + SOASetup.GetBasedOnAgentUserSecurityID(Rec."Agent User Security ID", true); + SendReply(InputAgentTaskMessage, Rec, SOASetup); + end; + + var + SOASetupCU: Codeunit "SOA Setup"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + TelemetryFailedToGetAgentTaskMessageAttachmentLbl: Label 'Failed to get agent task message attachment.', Locked = true; + TelemetryAttachmentAddedToEmailLbl: Label 'Attachment added to email.', Locked = true; + EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id'; + EmailReplyFailedErr: Label 'The email reply could not be sent.'; + + internal procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid; var AttemptCount: Integer): Boolean + var + SOAReplyAttempt: Record "SOA Reply Attempt"; + begin + SOAReplyAttempt.LockTable(); + if SOAReplyAttempt.Get(TaskId, MessageId) then begin + if SOAReplyAttempt."Attempt Count" >= GetMaxAttempts() then + exit(false); + + SOAReplyAttempt."Attempt Count" += 1; + SOAReplyAttempt.Modify(); + end else begin + SOAReplyAttempt."Task ID" := TaskId; + SOAReplyAttempt."Message ID" := MessageId; + SOAReplyAttempt."Attempt Count" := 1; + SOAReplyAttempt.Insert(); + end; + + AttemptCount := SOAReplyAttempt."Attempt Count"; + exit(true); + end; + + internal procedure ResetAttempts(TaskId: BigInteger; MessageId: Guid) + var + SOAReplyAttempt: Record "SOA Reply Attempt"; + begin + if SOAReplyAttempt.Get(TaskId, MessageId) then + SOAReplyAttempt.Delete(); + end; + + internal procedure IsExhausted(TaskId: BigInteger; MessageId: Guid): Boolean + var + SOAReplyAttempt: Record "SOA Reply Attempt"; + begin + if not SOAReplyAttempt.Get(TaskId, MessageId) then + exit(false); + + exit(SOAReplyAttempt."Attempt Count" >= GetMaxAttempts()); + end; + + internal procedure GetMaxAttempts(): Integer + begin + exit(5); + end; + + local procedure SendReply(InputAgentTaskMessage: Record "Agent Task Message"; OutputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup") + var + AgentMessage: Codeunit "Agent Message"; + Email: Codeunit Email; + EmailMessage: Codeunit "Email Message"; + Body: Text; + ErrorText: Text; + Subject: Text; + begin + Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID"); + Body := AgentMessage.GetText(OutputAgentTaskMessage); + EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID"); + AddMessageAttachments(EmailMessage, OutputAgentTaskMessage); + + if Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then + exit; + + ErrorText := GetLastErrorText(); + if ErrorText = '' then + ErrorText := EmailReplyFailedErr; + Error(ErrorText); + end; + + local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message") + var + AgentTaskFile: Record "Agent Task File"; + AgentTaskMessageAttachment: Record "Agent Task Message Attachment"; + AgentTaskFileInStream: InStream; + TelemetryDimensions: Dictionary of [Text, Text]; + begin + AgentTaskMessageAttachment.SetRange("Task ID", AgentTaskMessage."Task ID"); + AgentTaskMessageAttachment.SetRange("Message ID", AgentTaskMessage.ID); + if not AgentTaskMessageAttachment.FindSet() then + exit; + + repeat + if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then begin + FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentLbl, '', TelemetryDimensions); + exit; + end; + AgentTaskFile.CalcFields(Content); + //TODO: Refactor to a better interface + AgentTaskFile.Content.CreateInStream(AgentTaskFileInStream, TextEncoding::UTF8); + EmailMessage.AddAttachment(AgentTaskFile."File Name", AgentTaskFile."File MIME Type", AgentTaskFileInStream); + FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailLbl, TelemetryDimensions); + until AgentTaskMessageAttachment.Next() = 0; + end; +} diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al index 5c5b4a919e0..5077a0794c5 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al @@ -7,7 +7,6 @@ namespace Microsoft.Agent.SalesOrderAgent; using System.Agents; -using System.Email; using System.Telemetry; codeunit 4581 "SOA Send Replies" @@ -30,17 +29,13 @@ codeunit 4581 "SOA Send Replies" TelemetryEmailReplyFailedToSendLbl: Label 'Email reply failed to send.', Locked = true; TelemetryEmailReplyExternalIdEmptyLbl: Label 'Email reply failed to be sent due to input agent task message containing empty External Id.', Locked = true; TelemetryFailedToGetInputAgentTaskMessageLbl: Label 'Failed to get input agent task message.', Locked = true; - TelemetryFailedToGetAgentTaskMessageAttachmentLbl: Label 'Failed to get agent task message attachment.', Locked = true; - TelemetryAttachmentAddedToEmailLbl: Label 'Attachment added to email.', Locked = true; - EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id'; local procedure SendEmailReplies(SOASetup: Record "SOA Setup") var OutputAgentTaskMessage: Record "Agent Task Message"; - InputAgentTaskMessage: Record "Agent Task Message"; - EmailOutbox: Record "Email Outbox"; - AgentMessage: Codeunit "Agent Message"; + SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; TelemetryDimensions: Dictionary of [Text, Text]; + AttemptCount: Integer; begin AllSentSuccessfully := true; @@ -53,27 +48,16 @@ codeunit 4581 "SOA Send Replies" exit; repeat - Clear(EmailOutbox); - TelemetryDimensions.Set('AgentTaskID', Format(OutputAgentTaskMessage."Task ID")); - TelemetryDimensions.Set('AgentTaskMessageID', OutputAgentTaskMessage."ID"); - - if not InputAgentTaskMessage.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then begin - FeatureTelemetry.LogError('0000NDQ', SOASetupCU.GetFeatureName(), 'Get Input Agent Task Message', TelemetryFailedToGetInputAgentTaskMessageLbl, GetLastErrorCallStack(), TelemetryDimensions); - exit; - end; - if (InputAgentTaskMessage."External ID" = '') then begin - FeatureTelemetry.LogUsage('0000NDR', SOASetupCU.GetFeatureName(), TelemetryEmailReplyExternalIdEmptyLbl, TelemetryDimensions); - exit; - end; - - if TryReply(InputAgentTaskMessage, OutputAgentTaskMessage, SOASetup) then begin - AgentMessage.SetStatusToSent(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."ID"); - FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions); - end else begin + Clear(TelemetryDimensions); + TelemetryDimensions.Add('AgentTaskID', Format(OutputAgentTaskMessage."Task ID")); + TelemetryDimensions.Add('AgentTaskMessageID', OutputAgentTaskMessage."ID"); + + if SOAReplyRetryMgt.TryReserveAttempt(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID, AttemptCount) then begin + Commit(); + SetAttemptTelemetryDimensions(TelemetryDimensions, AttemptCount, SOAReplyRetryMgt.GetMaxAttempts()); + SendEmailReply(OutputAgentTaskMessage, TelemetryDimensions); + end else AllSentSuccessfully := false; - TelemetryDimensions.Set('Error', GetLastErrorText()); - FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, GetLastErrorCallStack(), TelemetryDimensions); - end; until OutputAgentTaskMessage.Next() = 0; end; @@ -82,44 +66,40 @@ codeunit 4581 "SOA Send Replies" exit(AllSentSuccessfully); end; - local procedure TryReply(InputAgentTaskMessage: Record "Agent Task Message"; OutputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup"): Boolean + local procedure SendEmailReply(OutputAgentTaskMessage: Record "Agent Task Message"; var TelemetryDimensions: Dictionary of [Text, Text]) var + InputAgentTaskMessage: Record "Agent Task Message"; AgentMessage: Codeunit "Agent Message"; - Email: Codeunit Email; - EmailMessage: Codeunit "Email Message"; - Body: Text; - Subject: Text; + SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; begin - Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID"); - Body := AgentMessage.GetText(OutputAgentTaskMessage); - EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID"); - AddMessageAttachments(EmailMessage, OutputAgentTaskMessage); + if not InputAgentTaskMessage.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then begin + AllSentSuccessfully := false; + FeatureTelemetry.LogError('0000NDQ', SOASetupCU.GetFeatureName(), 'Get Input Agent Task Message', TelemetryFailedToGetInputAgentTaskMessageLbl, GetLastErrorCallStack(), TelemetryDimensions); + exit; + end; - exit(Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector")); + if InputAgentTaskMessage."External ID" = '' then begin + AllSentSuccessfully := false; + FeatureTelemetry.LogUsage('0000NDR', SOASetupCU.GetFeatureName(), TelemetryEmailReplyExternalIdEmptyLbl, TelemetryDimensions); + exit; + end; + + ClearLastError(); + if Codeunit.Run(Codeunit::"SOA Reply Retry Mgt.", OutputAgentTaskMessage) then begin + AgentMessage.SetStatusToSent(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."ID"); + SOAReplyRetryMgt.ResetAttempts(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); + FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions); + end else begin + AllSentSuccessfully := false; + TelemetryDimensions.Set('Error', GetLastErrorText()); + FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, GetLastErrorCallStack(), TelemetryDimensions); + end; end; - local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message") - var - AgentTaskFile: Record "Agent Task File"; - AgentTaskMessageAttachment: Record "Agent Task Message Attachment"; - AgentTaskFileInStream: InStream; - TelemetryDimensions: Dictionary of [Text, Text]; + local procedure SetAttemptTelemetryDimensions(var TelemetryDimensions: Dictionary of [Text, Text]; AttemptCount: Integer; MaxAttempts: Integer) begin - AgentTaskMessageAttachment.SetRange("Task ID", AgentTaskMessage."Task ID"); - AgentTaskMessageAttachment.SetRange("Message ID", AgentTaskMessage.ID); - if not AgentTaskMessageAttachment.FindSet() then - exit; - - repeat - if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then begin - FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentLbl, '', TelemetryDimensions); - exit; - end; - AgentTaskFile.CalcFields(Content); - //TODO: Refactor to a better interface - AgentTaskFile.Content.CreateInStream(AgentTaskFileInStream, TextEncoding::UTF8); - EmailMessage.AddAttachment(AgentTaskFile."File Name", AgentTaskFile."File MIME Type", AgentTaskFileInStream); - FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailLbl, TelemetryDimensions); - until AgentTaskMessageAttachment.Next() = 0; + TelemetryDimensions.Set('AttemptCount', Format(AttemptCount)); + TelemetryDimensions.Set('MaxAttempts', Format(MaxAttempts)); + TelemetryDimensions.Set('AttemptsExhausted', Format(AttemptCount >= MaxAttempts)); end; } \ No newline at end of file diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al b/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al index 6d27d1626b4..dd6763b5902 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al @@ -20,6 +20,7 @@ permissionset 4406 "SOA - Objects" Permissions = tabledata "Contact" = R, tabledata "SOA Email" = RIM, + tabledata "SOA Reply Attempt" = RIMD, tabledata "SOA Task Contact Override" = RIM, page "Contact Card" = X, page "Contact List" = X, From d593f9d1f926089bf41de8e128302d42ca22bb63 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Mon, 20 Jul 2026 14:09:23 +0200 Subject: [PATCH 2/7] Address reply retry review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Integration/SOAReplyAttempt.Table.al | 3 + .../Integration/SOAReplyRetryMgt.Codeunit.al | 138 +++++++++++++++--- .../Integration/SOASendReplies.Codeunit.al | 33 +++-- .../Permissions/SOAObjects.PermissionSet.al | 2 +- 4 files changed, 144 insertions(+), 32 deletions(-) diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al index 3341b4b4b37..7dde65b3460 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyAttempt.Table.al @@ -20,13 +20,16 @@ table 4589 "SOA Reply Attempt" { field(1; "Task ID"; BigInteger) { + DataClassification = SystemMetadata; TableRelation = "Agent Task".ID; } field(2; "Message ID"; Guid) { + DataClassification = SystemMetadata; } field(3; "Attempt Count"; Integer) { + DataClassification = SystemMetadata; } } diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al index 2f4f5b85151..be64fced696 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al @@ -22,21 +22,50 @@ codeunit 4580 "SOA Reply Retry Mgt." var InputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup"; + AgentMessage: Codeunit "Agent Message"; begin + ClearRunResult(); + Rec.Get(Rec."Task ID", Rec.ID); + ValidateReplyMessage(Rec, SOASetup); + + if not TryReserveAttempt(Rec."Task ID", Rec.ID, AttemptCount) then + exit; + + AttemptReserved := true; InputAgentTaskMessage.Get(Rec."Task ID", Rec."Input Message ID"); - SOASetup.GetBasedOnAgentUserSecurityID(Rec."Agent User Security ID", true); - SendReply(InputAgentTaskMessage, Rec, SOASetup); + ReplySent := TrySendReply(InputAgentTaskMessage, Rec, SOASetup); + if not ReplySent then begin + if ReplyErrorText = '' then + ReplyErrorText := GetLastErrorText(); + if ReplyErrorCallStack = '' then + ReplyErrorCallStack := GetLastErrorCallStack(); + if ReplyErrorText = '' then + ReplyErrorText := EmailReplyFailedErr; + end; + + if ReplySent then begin + AgentMessage.SetStatusToSent(Rec."Task ID", Rec.ID); + DeleteAttempts(Rec."Task ID", Rec.ID); + end; end; var SOASetupCU: Codeunit "SOA Setup"; FeatureTelemetry: Codeunit "Feature Telemetry"; - TelemetryFailedToGetAgentTaskMessageAttachmentLbl: Label 'Failed to get agent task message attachment.', Locked = true; - TelemetryAttachmentAddedToEmailLbl: Label 'Attachment added to email.', Locked = true; + TelemetryFailedToGetAgentTaskMessageAttachmentTxt: Label 'Failed to get agent task message attachment.', Locked = true; + TelemetryAttachmentAddedToEmailTxt: Label 'Attachment added to email.', Locked = true; EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id'; EmailReplyFailedErr: Label 'The email reply could not be sent.'; - - internal procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid; var AttemptCount: Integer): Boolean + EmailReplyFailedDetailErr: Label 'The email reply could not be sent. %1', Comment = '%1 = detailed error message'; + ReplyNotAuthorizedErr: Label 'You are not authorized to send this reply.'; + InvalidReplyMessageErr: Label 'Only reviewed output messages can be sent as replies.'; + AttemptCount: Integer; + AttemptReserved: Boolean; + ReplySent: Boolean; + ReplyErrorCallStack: Text; + ReplyErrorText: Text; + + local procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid; var ReservedAttemptCount: Integer): Boolean var SOAReplyAttempt: Record "SOA Reply Attempt"; begin @@ -54,16 +83,18 @@ codeunit 4580 "SOA Reply Retry Mgt." SOAReplyAttempt.Insert(); end; - AttemptCount := SOAReplyAttempt."Attempt Count"; + ReservedAttemptCount := SOAReplyAttempt."Attempt Count"; exit(true); end; internal procedure ResetAttempts(TaskId: BigInteger; MessageId: Guid) var - SOAReplyAttempt: Record "SOA Reply Attempt"; + AgentTaskMessage: Record "Agent Task Message"; begin - if SOAReplyAttempt.Get(TaskId, MessageId) then - SOAReplyAttempt.Delete(); + AgentTaskMessage.Get(TaskId, MessageId); + ValidateMessageAccess(AgentTaskMessage); + + DeleteAttempts(TaskId, MessageId); end; internal procedure IsExhausted(TaskId: BigInteger; MessageId: Guid): Boolean @@ -81,13 +112,38 @@ codeunit 4580 "SOA Reply Retry Mgt." exit(5); end; - local procedure SendReply(InputAgentTaskMessage: Record "Agent Task Message"; OutputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup") + internal procedure WasAttemptReserved(): Boolean + begin + exit(AttemptReserved); + end; + + internal procedure WasReplySent(): Boolean + begin + exit(ReplySent); + end; + + internal procedure GetAttemptCount(): Integer + begin + exit(AttemptCount); + end; + + internal procedure GetReplyErrorText(): Text + begin + exit(ReplyErrorText); + end; + + internal procedure GetReplyErrorCallStack(): Text + begin + exit(ReplyErrorCallStack); + end; + + [TryFunction] + local procedure TrySendReply(InputAgentTaskMessage: Record "Agent Task Message"; OutputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup") var AgentMessage: Codeunit "Agent Message"; Email: Codeunit Email; EmailMessage: Codeunit "Email Message"; Body: Text; - ErrorText: Text; Subject: Text; begin Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID"); @@ -98,10 +154,11 @@ codeunit 4580 "SOA Reply Retry Mgt." if Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then exit; - ErrorText := GetLastErrorText(); - if ErrorText = '' then - ErrorText := EmailReplyFailedErr; - Error(ErrorText); + ReplyErrorText := GetLastErrorText(); + ReplyErrorCallStack := GetLastErrorCallStack(); + if ReplyErrorText = '' then + ReplyErrorText := EmailReplyFailedErr; + Error(EmailReplyFailedDetailErr, ReplyErrorText); end; local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message") @@ -118,14 +175,59 @@ codeunit 4580 "SOA Reply Retry Mgt." repeat if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then begin - FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentLbl, '', TelemetryDimensions); + FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentTxt, '', TelemetryDimensions); exit; end; AgentTaskFile.CalcFields(Content); //TODO: Refactor to a better interface AgentTaskFile.Content.CreateInStream(AgentTaskFileInStream, TextEncoding::UTF8); EmailMessage.AddAttachment(AgentTaskFile."File Name", AgentTaskFile."File MIME Type", AgentTaskFileInStream); - FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailLbl, TelemetryDimensions); + FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailTxt, TelemetryDimensions); until AgentTaskMessageAttachment.Next() = 0; end; + + local procedure ValidateReplyMessage(AgentTaskMessage: Record "Agent Task Message"; var SOASetup: Record "SOA Setup") + begin + if (AgentTaskMessage.Type <> AgentTaskMessage.Type::Output) or (AgentTaskMessage.Status <> AgentTaskMessage.Status::Reviewed) then + Error(InvalidReplyMessageErr); + + ValidateMessageAccess(AgentTaskMessage, SOASetup); + end; + + local procedure ValidateMessageAccess(AgentTaskMessage: Record "Agent Task Message") + var + SOASetup: Record "SOA Setup"; + begin + ValidateMessageAccess(AgentTaskMessage, SOASetup); + end; + + local procedure ValidateMessageAccess(AgentTaskMessage: Record "Agent Task Message"; var SOASetup: Record "SOA Setup") + var + OwnerUserSecurityID: Guid; + begin + SOASetup.GetBasedOnAgentUserSecurityID(AgentTaskMessage."Agent User Security ID", true); + OwnerUserSecurityID := SOASetup."Owner User Security ID"; + if IsNullGuid(OwnerUserSecurityID) then + OwnerUserSecurityID := SOASetup."User Security ID"; + + if (UserSecurityId() <> OwnerUserSecurityID) and (UserSecurityId() <> SOASetup."User Security ID") then + Error(ReplyNotAuthorizedErr); + end; + + local procedure ClearRunResult() + begin + Clear(AttemptCount); + Clear(AttemptReserved); + Clear(ReplySent); + Clear(ReplyErrorCallStack); + Clear(ReplyErrorText); + end; + + local procedure DeleteAttempts(TaskId: BigInteger; MessageId: Guid) + var + SOAReplyAttempt: Record "SOA Reply Attempt"; + begin + if SOAReplyAttempt.Get(TaskId, MessageId) then + SOAReplyAttempt.Delete(); + end; } diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al index 5077a0794c5..fb19841e29e 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al @@ -35,7 +35,6 @@ codeunit 4581 "SOA Send Replies" OutputAgentTaskMessage: Record "Agent Task Message"; SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; TelemetryDimensions: Dictionary of [Text, Text]; - AttemptCount: Integer; begin AllSentSuccessfully := true; @@ -52,12 +51,10 @@ codeunit 4581 "SOA Send Replies" TelemetryDimensions.Add('AgentTaskID', Format(OutputAgentTaskMessage."Task ID")); TelemetryDimensions.Add('AgentTaskMessageID', OutputAgentTaskMessage."ID"); - if SOAReplyRetryMgt.TryReserveAttempt(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID, AttemptCount) then begin - Commit(); - SetAttemptTelemetryDimensions(TelemetryDimensions, AttemptCount, SOAReplyRetryMgt.GetMaxAttempts()); + if SOAReplyRetryMgt.IsExhausted(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID) then + AllSentSuccessfully := false + else SendEmailReply(OutputAgentTaskMessage, TelemetryDimensions); - end else - AllSentSuccessfully := false; until OutputAgentTaskMessage.Next() = 0; end; @@ -69,7 +66,6 @@ codeunit 4581 "SOA Send Replies" local procedure SendEmailReply(OutputAgentTaskMessage: Record "Agent Task Message"; var TelemetryDimensions: Dictionary of [Text, Text]) var InputAgentTaskMessage: Record "Agent Task Message"; - AgentMessage: Codeunit "Agent Message"; SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; begin if not InputAgentTaskMessage.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then begin @@ -84,15 +80,26 @@ codeunit 4581 "SOA Send Replies" exit; end; - ClearLastError(); - if Codeunit.Run(Codeunit::"SOA Reply Retry Mgt.", OutputAgentTaskMessage) then begin - AgentMessage.SetStatusToSent(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."ID"); - SOAReplyRetryMgt.ResetAttempts(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); - FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions); - end else begin + Clear(SOAReplyRetryMgt); + if not SOAReplyRetryMgt.Run(OutputAgentTaskMessage) then begin AllSentSuccessfully := false; TelemetryDimensions.Set('Error', GetLastErrorText()); FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, GetLastErrorCallStack(), TelemetryDimensions); + exit; + end; + + if not SOAReplyRetryMgt.WasAttemptReserved() then begin + AllSentSuccessfully := false; + exit; + end; + + SetAttemptTelemetryDimensions(TelemetryDimensions, SOAReplyRetryMgt.GetAttemptCount(), SOAReplyRetryMgt.GetMaxAttempts()); + if SOAReplyRetryMgt.WasReplySent() then + FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions) + else begin + AllSentSuccessfully := false; + TelemetryDimensions.Set('Error', SOAReplyRetryMgt.GetReplyErrorText()); + FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, SOAReplyRetryMgt.GetReplyErrorCallStack(), TelemetryDimensions); end; end; diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al b/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al index dd6763b5902..5ad55218c59 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Permissions/SOAObjects.PermissionSet.al @@ -20,7 +20,7 @@ permissionset 4406 "SOA - Objects" Permissions = tabledata "Contact" = R, tabledata "SOA Email" = RIM, - tabledata "SOA Reply Attempt" = RIMD, + tabledata "SOA Reply Attempt" = rimd, tabledata "SOA Task Contact Override" = RIM, page "Contact Card" = X, page "Contact List" = X, From 5efecfbf0ebb6306c8f96c13cce79a19a91a6e61 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Mon, 20 Jul 2026 14:58:04 +0200 Subject: [PATCH 3/7] Address remaining reply retry feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Integration/SOAReplyRetryMgt.Codeunit.al | 28 ++++++++----------- .../Integration/SOASendReplies.Codeunit.al | 12 +------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al index be64fced696..a919297a389 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al @@ -10,7 +10,7 @@ using System.Agents; using System.Email; using System.Telemetry; -codeunit 4580 "SOA Reply Retry Mgt." +codeunit 4418 "SOA Reply Retry Mgt." { Access = Internal; InherentEntitlements = X; @@ -28,7 +28,7 @@ codeunit 4580 "SOA Reply Retry Mgt." Rec.Get(Rec."Task ID", Rec.ID); ValidateReplyMessage(Rec, SOASetup); - if not TryReserveAttempt(Rec."Task ID", Rec.ID, AttemptCount) then + if not TryReserveAttempt(Rec."Task ID", Rec.ID) then exit; AttemptReserved := true; @@ -59,13 +59,12 @@ codeunit 4580 "SOA Reply Retry Mgt." EmailReplyFailedDetailErr: Label 'The email reply could not be sent. %1', Comment = '%1 = detailed error message'; ReplyNotAuthorizedErr: Label 'You are not authorized to send this reply.'; InvalidReplyMessageErr: Label 'Only reviewed output messages can be sent as replies.'; - AttemptCount: Integer; AttemptReserved: Boolean; ReplySent: Boolean; ReplyErrorCallStack: Text; ReplyErrorText: Text; - local procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid; var ReservedAttemptCount: Integer): Boolean + local procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid): Boolean var SOAReplyAttempt: Record "SOA Reply Attempt"; begin @@ -83,7 +82,6 @@ codeunit 4580 "SOA Reply Retry Mgt." SOAReplyAttempt.Insert(); end; - ReservedAttemptCount := SOAReplyAttempt."Attempt Count"; exit(true); end; @@ -122,16 +120,6 @@ codeunit 4580 "SOA Reply Retry Mgt." exit(ReplySent); end; - internal procedure GetAttemptCount(): Integer - begin - exit(AttemptCount); - end; - - internal procedure GetReplyErrorText(): Text - begin - exit(ReplyErrorText); - end; - internal procedure GetReplyErrorCallStack(): Text begin exit(ReplyErrorCallStack); @@ -216,7 +204,6 @@ codeunit 4580 "SOA Reply Retry Mgt." local procedure ClearRunResult() begin - Clear(AttemptCount); Clear(AttemptReserved); Clear(ReplySent); Clear(ReplyErrorCallStack); @@ -230,4 +217,13 @@ codeunit 4580 "SOA Reply Retry Mgt." if SOAReplyAttempt.Get(TaskId, MessageId) then SOAReplyAttempt.Delete(); end; + + [EventSubscriber(ObjectType::Table, Database::"Agent Task Message", 'OnAfterDeleteEvent', '', false, false)] + local procedure DeleteAttemptsOnAfterDeleteAgentTaskMessage(var Rec: Record "Agent Task Message"; RunTrigger: Boolean) + begin + if Rec.IsTemporary() then + exit; + + DeleteAttempts(Rec."Task ID", Rec.ID); + end; } diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al index fb19841e29e..b58d7ea2081 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al @@ -76,14 +76,13 @@ codeunit 4581 "SOA Send Replies" if InputAgentTaskMessage."External ID" = '' then begin AllSentSuccessfully := false; - FeatureTelemetry.LogUsage('0000NDR', SOASetupCU.GetFeatureName(), TelemetryEmailReplyExternalIdEmptyLbl, TelemetryDimensions); + FeatureTelemetry.LogError('0000NDR', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyExternalIdEmptyLbl, '', TelemetryDimensions); exit; end; Clear(SOAReplyRetryMgt); if not SOAReplyRetryMgt.Run(OutputAgentTaskMessage) then begin AllSentSuccessfully := false; - TelemetryDimensions.Set('Error', GetLastErrorText()); FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, GetLastErrorCallStack(), TelemetryDimensions); exit; end; @@ -93,20 +92,11 @@ codeunit 4581 "SOA Send Replies" exit; end; - SetAttemptTelemetryDimensions(TelemetryDimensions, SOAReplyRetryMgt.GetAttemptCount(), SOAReplyRetryMgt.GetMaxAttempts()); if SOAReplyRetryMgt.WasReplySent() then FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions) else begin AllSentSuccessfully := false; - TelemetryDimensions.Set('Error', SOAReplyRetryMgt.GetReplyErrorText()); FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, SOAReplyRetryMgt.GetReplyErrorCallStack(), TelemetryDimensions); end; end; - - local procedure SetAttemptTelemetryDimensions(var TelemetryDimensions: Dictionary of [Text, Text]; AttemptCount: Integer; MaxAttempts: Integer) - begin - TelemetryDimensions.Set('AttemptCount', Format(AttemptCount)); - TelemetryDimensions.Set('MaxAttempts', Format(MaxAttempts)); - TelemetryDimensions.Set('AttemptsExhausted', Format(AttemptCount >= MaxAttempts)); - end; } \ No newline at end of file From 7854f9499045357f268e6347e77b74223c2c1a02 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Wed, 22 Jul 2026 10:40:37 +0200 Subject: [PATCH 4/7] Fix send mailbox --- .../src/Integration/SOADispatcher.Codeunit.al | 8 +- .../Integration/SOAReplyRetryMgt.Codeunit.al | 155 ++---------------- .../Integration/SOASendReplies.Codeunit.al | 36 ++-- .../src/Integration/SOASendReply.Codeunit.al | 81 +++++++++ 4 files changed, 122 insertions(+), 158 deletions(-) create mode 100644 src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReply.Codeunit.al diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al index b0cf7a648e6..7d997e6331a 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al @@ -11,12 +11,13 @@ using System.AI; using System.Azure.Identity; using System.Environment; using System.Telemetry; +using System.Threading; using System.Utilities; codeunit 4586 "SOA Dispatcher" { Access = Internal; - TableNo = "SOA Setup"; + TableNo = "Job Queue Entry"; InherentEntitlements = X; InherentPermissions = X; @@ -33,8 +34,11 @@ codeunit 4586 "SOA Dispatcher" TelemetrySendEmailRepliesFailedLbl: Label 'Email replies failed to be sent', Locked = true; trigger OnRun() + var + SOASetup: Record "SOA Setup"; begin - RunSOAgent(Rec); + SOASetup.FindFirst(); + RunSOAgent(SOASetup); end; procedure RunSOAgent(Setup: Record "SOA Setup") diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al index a919297a389..fefea8831a4 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al @@ -7,8 +7,6 @@ namespace Microsoft.Agent.SalesOrderAgent; using System.Agents; -using System.Email; -using System.Telemetry; codeunit 4418 "SOA Reply Retry Mgt." { @@ -16,73 +14,26 @@ codeunit 4418 "SOA Reply Retry Mgt." InherentEntitlements = X; InherentPermissions = X; Permissions = tabledata "SOA Reply Attempt" = RIMD; - TableNo = "Agent Task Message"; - trigger OnRun() var - InputAgentTaskMessage: Record "Agent Task Message"; - SOASetup: Record "SOA Setup"; - AgentMessage: Codeunit "Agent Message"; - begin - ClearRunResult(); - Rec.Get(Rec."Task ID", Rec.ID); - ValidateReplyMessage(Rec, SOASetup); - - if not TryReserveAttempt(Rec."Task ID", Rec.ID) then - exit; - - AttemptReserved := true; - InputAgentTaskMessage.Get(Rec."Task ID", Rec."Input Message ID"); - ReplySent := TrySendReply(InputAgentTaskMessage, Rec, SOASetup); - if not ReplySent then begin - if ReplyErrorText = '' then - ReplyErrorText := GetLastErrorText(); - if ReplyErrorCallStack = '' then - ReplyErrorCallStack := GetLastErrorCallStack(); - if ReplyErrorText = '' then - ReplyErrorText := EmailReplyFailedErr; - end; - - if ReplySent then begin - AgentMessage.SetStatusToSent(Rec."Task ID", Rec.ID); - DeleteAttempts(Rec."Task ID", Rec.ID); - end; - end; - - var - SOASetupCU: Codeunit "SOA Setup"; - FeatureTelemetry: Codeunit "Feature Telemetry"; - TelemetryFailedToGetAgentTaskMessageAttachmentTxt: Label 'Failed to get agent task message attachment.', Locked = true; - TelemetryAttachmentAddedToEmailTxt: Label 'Attachment added to email.', Locked = true; - EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id'; - EmailReplyFailedErr: Label 'The email reply could not be sent.'; - EmailReplyFailedDetailErr: Label 'The email reply could not be sent. %1', Comment = '%1 = detailed error message'; ReplyNotAuthorizedErr: Label 'You are not authorized to send this reply.'; - InvalidReplyMessageErr: Label 'Only reviewed output messages can be sent as replies.'; - AttemptReserved: Boolean; - ReplySent: Boolean; - ReplyErrorCallStack: Text; - ReplyErrorText: Text; - local procedure TryReserveAttempt(TaskId: BigInteger; MessageId: Guid): Boolean + internal procedure RegisterFailedAttempt(TaskId: BigInteger; MessageId: Guid) var SOAReplyAttempt: Record "SOA Reply Attempt"; begin SOAReplyAttempt.LockTable(); if SOAReplyAttempt.Get(TaskId, MessageId) then begin - if SOAReplyAttempt."Attempt Count" >= GetMaxAttempts() then - exit(false); - - SOAReplyAttempt."Attempt Count" += 1; - SOAReplyAttempt.Modify(); + if SOAReplyAttempt."Attempt Count" < GetMaxAttempts() then begin + SOAReplyAttempt."Attempt Count" += 1; + SOAReplyAttempt.Modify(); + end; end else begin SOAReplyAttempt."Task ID" := TaskId; SOAReplyAttempt."Message ID" := MessageId; SOAReplyAttempt."Attempt Count" := 1; SOAReplyAttempt.Insert(); end; - - exit(true); end; internal procedure ResetAttempts(TaskId: BigInteger; MessageId: Guid) @@ -92,7 +43,7 @@ codeunit 4418 "SOA Reply Retry Mgt." AgentTaskMessage.Get(TaskId, MessageId); ValidateMessageAccess(AgentTaskMessage); - DeleteAttempts(TaskId, MessageId); + ClearAttempts(TaskId, MessageId); end; internal procedure IsExhausted(TaskId: BigInteger; MessageId: Guid): Boolean @@ -105,81 +56,17 @@ codeunit 4418 "SOA Reply Retry Mgt." exit(SOAReplyAttempt."Attempt Count" >= GetMaxAttempts()); end; - internal procedure GetMaxAttempts(): Integer - begin - exit(5); - end; - - internal procedure WasAttemptReserved(): Boolean - begin - exit(AttemptReserved); - end; - - internal procedure WasReplySent(): Boolean - begin - exit(ReplySent); - end; - - internal procedure GetReplyErrorCallStack(): Text - begin - exit(ReplyErrorCallStack); - end; - - [TryFunction] - local procedure TrySendReply(InputAgentTaskMessage: Record "Agent Task Message"; OutputAgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup") + internal procedure ClearAttempts(TaskId: BigInteger; MessageId: Guid) var - AgentMessage: Codeunit "Agent Message"; - Email: Codeunit Email; - EmailMessage: Codeunit "Email Message"; - Body: Text; - Subject: Text; - begin - Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID"); - Body := AgentMessage.GetText(OutputAgentTaskMessage); - EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID"); - AddMessageAttachments(EmailMessage, OutputAgentTaskMessage); - - if Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then - exit; - - ReplyErrorText := GetLastErrorText(); - ReplyErrorCallStack := GetLastErrorCallStack(); - if ReplyErrorText = '' then - ReplyErrorText := EmailReplyFailedErr; - Error(EmailReplyFailedDetailErr, ReplyErrorText); - end; - - local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message") - var - AgentTaskFile: Record "Agent Task File"; - AgentTaskMessageAttachment: Record "Agent Task Message Attachment"; - AgentTaskFileInStream: InStream; - TelemetryDimensions: Dictionary of [Text, Text]; + SOAReplyAttempt: Record "SOA Reply Attempt"; begin - AgentTaskMessageAttachment.SetRange("Task ID", AgentTaskMessage."Task ID"); - AgentTaskMessageAttachment.SetRange("Message ID", AgentTaskMessage.ID); - if not AgentTaskMessageAttachment.FindSet() then - exit; - - repeat - if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then begin - FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentTxt, '', TelemetryDimensions); - exit; - end; - AgentTaskFile.CalcFields(Content); - //TODO: Refactor to a better interface - AgentTaskFile.Content.CreateInStream(AgentTaskFileInStream, TextEncoding::UTF8); - EmailMessage.AddAttachment(AgentTaskFile."File Name", AgentTaskFile."File MIME Type", AgentTaskFileInStream); - FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailTxt, TelemetryDimensions); - until AgentTaskMessageAttachment.Next() = 0; + if SOAReplyAttempt.Get(TaskId, MessageId) then + SOAReplyAttempt.Delete(); end; - local procedure ValidateReplyMessage(AgentTaskMessage: Record "Agent Task Message"; var SOASetup: Record "SOA Setup") + internal procedure GetMaxAttempts(): Integer begin - if (AgentTaskMessage.Type <> AgentTaskMessage.Type::Output) or (AgentTaskMessage.Status <> AgentTaskMessage.Status::Reviewed) then - Error(InvalidReplyMessageErr); - - ValidateMessageAccess(AgentTaskMessage, SOASetup); + exit(5); end; local procedure ValidateMessageAccess(AgentTaskMessage: Record "Agent Task Message") @@ -202,28 +89,12 @@ codeunit 4418 "SOA Reply Retry Mgt." Error(ReplyNotAuthorizedErr); end; - local procedure ClearRunResult() - begin - Clear(AttemptReserved); - Clear(ReplySent); - Clear(ReplyErrorCallStack); - Clear(ReplyErrorText); - end; - - local procedure DeleteAttempts(TaskId: BigInteger; MessageId: Guid) - var - SOAReplyAttempt: Record "SOA Reply Attempt"; - begin - if SOAReplyAttempt.Get(TaskId, MessageId) then - SOAReplyAttempt.Delete(); - end; - [EventSubscriber(ObjectType::Table, Database::"Agent Task Message", 'OnAfterDeleteEvent', '', false, false)] local procedure DeleteAttemptsOnAfterDeleteAgentTaskMessage(var Rec: Record "Agent Task Message"; RunTrigger: Boolean) begin if Rec.IsTemporary() then exit; - DeleteAttempts(Rec."Task ID", Rec.ID); + ClearAttempts(Rec."Task ID", Rec.ID); end; } diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al index b58d7ea2081..e781917b3ed 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al @@ -67,36 +67,44 @@ codeunit 4581 "SOA Send Replies" var InputAgentTaskMessage: Record "Agent Task Message"; SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; + SOASendReply: Codeunit "SOA Send Reply"; + ErrorCallStack: Text; + ErrorText: Text; begin if not InputAgentTaskMessage.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then begin AllSentSuccessfully := false; + RegisterFailedAttempt(OutputAgentTaskMessage); FeatureTelemetry.LogError('0000NDQ', SOASetupCU.GetFeatureName(), 'Get Input Agent Task Message', TelemetryFailedToGetInputAgentTaskMessageLbl, GetLastErrorCallStack(), TelemetryDimensions); exit; end; if InputAgentTaskMessage."External ID" = '' then begin AllSentSuccessfully := false; + RegisterFailedAttempt(OutputAgentTaskMessage); FeatureTelemetry.LogError('0000NDR', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyExternalIdEmptyLbl, '', TelemetryDimensions); exit; end; - Clear(SOAReplyRetryMgt); - if not SOAReplyRetryMgt.Run(OutputAgentTaskMessage) then begin - AllSentSuccessfully := false; - FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, GetLastErrorCallStack(), TelemetryDimensions); + if SOASendReply.Run(OutputAgentTaskMessage) then begin + SOAReplyRetryMgt.ClearAttempts(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); + Commit(); + FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions); exit; end; - if not SOAReplyRetryMgt.WasAttemptReserved() then begin - AllSentSuccessfully := false; - exit; - end; + ErrorText := GetLastErrorText(); + ErrorCallStack := GetLastErrorCallStack(); + AllSentSuccessfully := false; + RegisterFailedAttempt(OutputAgentTaskMessage); + TelemetryDimensions.Set('Error', ErrorText); + FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, ErrorCallStack, TelemetryDimensions); + end; - if SOAReplyRetryMgt.WasReplySent() then - FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions) - else begin - AllSentSuccessfully := false; - FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, SOAReplyRetryMgt.GetReplyErrorCallStack(), TelemetryDimensions); - end; + local procedure RegisterFailedAttempt(OutputAgentTaskMessage: Record "Agent Task Message") + var + SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; + begin + SOAReplyRetryMgt.RegisterFailedAttempt(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); + Commit(); end; } \ No newline at end of file diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReply.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReply.Codeunit.al new file mode 100644 index 00000000000..f8d7b8e2191 --- /dev/null +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReply.Codeunit.al @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +#pragma warning disable AS0007 +namespace Microsoft.Agent.SalesOrderAgent; + +using System.Agents; +using System.Email; +using System.Telemetry; + +codeunit 4419 "SOA Send Reply" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + TableNo = "Agent Task Message"; + + trigger OnRun() + var + InputAgentTaskMessage: Record "Agent Task Message"; + SOASetup: Record "SOA Setup"; + AgentMessage: Codeunit "Agent Message"; + Email: Codeunit Email; + EmailMessage: Codeunit "Email Message"; + Body: Text; + Subject: Text; + begin + Rec.Get(Rec."Task ID", Rec.ID); + if (Rec.Type <> Rec.Type::Output) or (Rec.Status <> Rec.Status::Reviewed) then + Error(InvalidReplyMessageErr); + + InputAgentTaskMessage.Get(Rec."Task ID", Rec."Input Message ID"); + SOASetup.GetBasedOnAgentUserSecurityID(Rec."Agent User Security ID", true); + + Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID"); + Body := AgentMessage.GetText(Rec); + EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID"); + AddMessageAttachments(EmailMessage, Rec); + + if not Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then + Error(EmailReplyFailedErr); + + AgentMessage.SetStatusToSent(Rec."Task ID", Rec.ID); + end; + + var + SOASetupCU: Codeunit "SOA Setup"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + TelemetryFailedToGetAgentTaskMessageAttachmentLbl: Label 'Failed to get agent task message attachment.', Locked = true; + TelemetryAttachmentAddedToEmailLbl: Label 'Attachment added to email.', Locked = true; + EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id'; + EmailReplyFailedErr: Label 'The email reply could not be sent.'; + InvalidReplyMessageErr: Label 'Only reviewed output messages can be sent as replies.'; + + local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message") + var + AgentTaskFile: Record "Agent Task File"; + AgentTaskMessageAttachment: Record "Agent Task Message Attachment"; + AgentTaskFileInStream: InStream; + TelemetryDimensions: Dictionary of [Text, Text]; + begin + AgentTaskMessageAttachment.SetRange("Task ID", AgentTaskMessage."Task ID"); + AgentTaskMessageAttachment.SetRange("Message ID", AgentTaskMessage.ID); + if not AgentTaskMessageAttachment.FindSet() then + exit; + + repeat + if not AgentTaskFile.Get(AgentTaskMessageAttachment."Task ID", AgentTaskMessageAttachment."File ID") then begin + FeatureTelemetry.LogError('0000NE7', SOASetupCU.GetFeatureName(), 'Get Agent Task Message Attachment', TelemetryFailedToGetAgentTaskMessageAttachmentLbl, '', TelemetryDimensions); + exit; + end; + AgentTaskFile.CalcFields(Content); + //TODO: Refactor to a better interface + AgentTaskFile.Content.CreateInStream(AgentTaskFileInStream, TextEncoding::UTF8); + EmailMessage.AddAttachment(AgentTaskFile."File Name", AgentTaskFile."File MIME Type", AgentTaskFileInStream); + FeatureTelemetry.LogUsage('0000NE8', SOASetupCU.GetFeatureName(), TelemetryAttachmentAddedToEmailLbl, TelemetryDimensions); + until AgentTaskMessageAttachment.Next() = 0; + end; +} From 266f4a96f92b84c2933dace24dea0d64bbcb0131 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Wed, 22 Jul 2026 11:48:16 +0200 Subject: [PATCH 5/7] Revert test code --- .../app/src/Integration/SOADispatcher.Codeunit.al | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al index 7d997e6331a..b0cf7a648e6 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOADispatcher.Codeunit.al @@ -11,13 +11,12 @@ using System.AI; using System.Azure.Identity; using System.Environment; using System.Telemetry; -using System.Threading; using System.Utilities; codeunit 4586 "SOA Dispatcher" { Access = Internal; - TableNo = "Job Queue Entry"; + TableNo = "SOA Setup"; InherentEntitlements = X; InherentPermissions = X; @@ -34,11 +33,8 @@ codeunit 4586 "SOA Dispatcher" TelemetrySendEmailRepliesFailedLbl: Label 'Email replies failed to be sent', Locked = true; trigger OnRun() - var - SOASetup: Record "SOA Setup"; begin - SOASetup.FindFirst(); - RunSOAgent(SOASetup); + RunSOAgent(Rec); end; procedure RunSOAgent(Setup: Record "SOA Setup") From cad3801a9528eac20cd8dffccc5c24be6a227a3c Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Wed, 22 Jul 2026 16:05:40 +0200 Subject: [PATCH 6/7] Page optimised --- .../src/Integration/SOAEmailMessage.Page.al | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al index a6290d345bb..3f516f3ead6 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAEmailMessage.Page.al @@ -184,10 +184,26 @@ page 4404 "SOA Email Message" Editable = false; } } + + group(SendingStatusGroup) + { + ShowCaption = false; + Visible = RetrySendingVisible; + + field(SendingStatus; SendingStatusTxt) + { + ApplicationArea = All; + Caption = 'Sending status'; + ToolTip = 'Specifies that the reply could not be sent after all retry attempts.'; + Editable = false; + Style = Unfavorable; + } + } } } } } + group(Message) { Caption = 'Message'; @@ -269,6 +285,17 @@ page 4404 "SOA Email Message" end; } } + area(Promoted) + { + group(Category_Process) + { + Caption = 'Process'; + + actionref(RetrySending_Promoted; RetrySending) + { + } + } + } } trigger OnAfterGetRecord() @@ -290,6 +317,10 @@ page 4404 "SOA Email Message" UpdateEmailFields(EmailAddress); UpdateContactInformation(EmailAddress); RetrySendingVisible := (Rec.Type = Rec.Type::Output) and (Rec.Status = Rec.Status::Reviewed) and SOAReplyRetryMgt.IsExhausted(Rec."Task ID", Rec.ID); + if RetrySendingVisible then + SendingStatusTxt := SendingFailedTxt + else + Clear(SendingStatusTxt); CurrPage.Attachments.Page.LoadRecords(Rec); end; @@ -452,10 +483,12 @@ page 4404 "SOA Email Message" AttachmentsVisible: Boolean; BlockedStatusVisible: Boolean; RetrySendingVisible: Boolean; + SendingStatusTxt: Text; OutgoingMessageTxt: Label 'Outgoing email'; IncomingMessageTxt: Label 'Incoming email'; SelectContactOrCreateLbl: Label 'Select an existing contact, or create a new one'; ShowAttachmentLbl: Label 'Show attachments (%1)', Comment = '%1 = Attachment count'; + SendingFailedTxt: Label 'Failed to send'; RetrySendingQst: Label 'Do you want to retry sending this reply?'; RetrySendingScheduledMsg: Label 'The reply will be retried during the next agent run.'; } \ No newline at end of file From 6805b2d58e037bf1494e712f95692cf96da29331 Mon Sep 17 00:00:00 2001 From: Qasim Ikram Date: Wed, 22 Jul 2026 17:02:17 +0200 Subject: [PATCH 7/7] Address reply retry review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: cda27b36-a839-43cf-9734-41c2648954ed --- .../Integration/SOAReplyRetryMgt.Codeunit.al | 3 ++ .../Integration/SOASendReplies.Codeunit.al | 41 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al index fefea8831a4..6e5fb92701f 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOAReplyRetryMgt.Codeunit.al @@ -95,6 +95,9 @@ codeunit 4418 "SOA Reply Retry Mgt." if Rec.IsTemporary() then exit; + if Rec.Type <> Rec.Type::Output then + exit; + ClearAttempts(Rec."Task ID", Rec.ID); end; } diff --git a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al index e781917b3ed..328ab6435d5 100644 --- a/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al +++ b/src/Apps/W1/SalesOrderAgent/app/src/Integration/SOASendReplies.Codeunit.al @@ -33,6 +33,8 @@ codeunit 4581 "SOA Send Replies" local procedure SendEmailReplies(SOASetup: Record "SOA Setup") var OutputAgentTaskMessage: Record "Agent Task Message"; + TempFailedReplyAttempt: Record "SOA Reply Attempt" temporary; + TempSuccessfulReplyAttempt: Record "SOA Reply Attempt" temporary; SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; TelemetryDimensions: Dictionary of [Text, Text]; begin @@ -54,8 +56,10 @@ codeunit 4581 "SOA Send Replies" if SOAReplyRetryMgt.IsExhausted(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID) then AllSentSuccessfully := false else - SendEmailReply(OutputAgentTaskMessage, TelemetryDimensions); + SendEmailReply(OutputAgentTaskMessage, TelemetryDimensions, TempFailedReplyAttempt, TempSuccessfulReplyAttempt); until OutputAgentTaskMessage.Next() = 0; + + ApplyRetryUpdates(TempFailedReplyAttempt, TempSuccessfulReplyAttempt); end; procedure GetAllSentSuccessfully(): Boolean @@ -63,48 +67,61 @@ codeunit 4581 "SOA Send Replies" exit(AllSentSuccessfully); end; - local procedure SendEmailReply(OutputAgentTaskMessage: Record "Agent Task Message"; var TelemetryDimensions: Dictionary of [Text, Text]) + local procedure SendEmailReply(OutputAgentTaskMessage: Record "Agent Task Message"; var TelemetryDimensions: Dictionary of [Text, Text]; var TempFailedReplyAttempt: Record "SOA Reply Attempt" temporary; var TempSuccessfulReplyAttempt: Record "SOA Reply Attempt" temporary) var InputAgentTaskMessage: Record "Agent Task Message"; - SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; SOASendReply: Codeunit "SOA Send Reply"; ErrorCallStack: Text; ErrorText: Text; begin if not InputAgentTaskMessage.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then begin AllSentSuccessfully := false; - RegisterFailedAttempt(OutputAgentTaskMessage); + AddRetryUpdate(TempFailedReplyAttempt, OutputAgentTaskMessage); FeatureTelemetry.LogError('0000NDQ', SOASetupCU.GetFeatureName(), 'Get Input Agent Task Message', TelemetryFailedToGetInputAgentTaskMessageLbl, GetLastErrorCallStack(), TelemetryDimensions); exit; end; if InputAgentTaskMessage."External ID" = '' then begin AllSentSuccessfully := false; - RegisterFailedAttempt(OutputAgentTaskMessage); + AddRetryUpdate(TempFailedReplyAttempt, OutputAgentTaskMessage); FeatureTelemetry.LogError('0000NDR', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyExternalIdEmptyLbl, '', TelemetryDimensions); exit; end; if SOASendReply.Run(OutputAgentTaskMessage) then begin - SOAReplyRetryMgt.ClearAttempts(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); - Commit(); + AddRetryUpdate(TempSuccessfulReplyAttempt, OutputAgentTaskMessage); FeatureTelemetry.LogUsage('0000NDS', SOASetupCU.GetFeatureName(), TelemetryEmailReplySentLbl, TelemetryDimensions); exit; end; - ErrorText := GetLastErrorText(); + ErrorText := GetLastErrorText(true); ErrorCallStack := GetLastErrorCallStack(); AllSentSuccessfully := false; - RegisterFailedAttempt(OutputAgentTaskMessage); + AddRetryUpdate(TempFailedReplyAttempt, OutputAgentTaskMessage); TelemetryDimensions.Set('Error', ErrorText); FeatureTelemetry.LogError('0000OAB', SOASetupCU.GetFeatureName(), 'Send Email Reply', TelemetryEmailReplyFailedToSendLbl, ErrorCallStack, TelemetryDimensions); end; - local procedure RegisterFailedAttempt(OutputAgentTaskMessage: Record "Agent Task Message") + local procedure AddRetryUpdate(var TempSOAReplyAttempt: Record "SOA Reply Attempt" temporary; OutputAgentTaskMessage: Record "Agent Task Message") + begin + TempSOAReplyAttempt.Init(); + TempSOAReplyAttempt."Task ID" := OutputAgentTaskMessage."Task ID"; + TempSOAReplyAttempt."Message ID" := OutputAgentTaskMessage.ID; + TempSOAReplyAttempt.Insert(); + end; + + local procedure ApplyRetryUpdates(var TempFailedReplyAttempt: Record "SOA Reply Attempt" temporary; var TempSuccessfulReplyAttempt: Record "SOA Reply Attempt" temporary) var SOAReplyRetryMgt: Codeunit "SOA Reply Retry Mgt."; begin - SOAReplyRetryMgt.RegisterFailedAttempt(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage.ID); - Commit(); + if TempFailedReplyAttempt.FindSet() then + repeat + SOAReplyRetryMgt.RegisterFailedAttempt(TempFailedReplyAttempt."Task ID", TempFailedReplyAttempt."Message ID"); + until TempFailedReplyAttempt.Next() = 0; + + if TempSuccessfulReplyAttempt.FindSet() then + repeat + SOAReplyRetryMgt.ClearAttempts(TempSuccessfulReplyAttempt."Task ID", TempSuccessfulReplyAttempt."Message ID"); + until TempSuccessfulReplyAttempt.Next() = 0; end; } \ No newline at end of file