-
Notifications
You must be signed in to change notification settings - Fork 372
Agent & Email Integration Sample #338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
msft-sam
merged 2 commits into
microsoft:master
from
darjoo:private/dajoo/sampleemailintegration
Feb 13, 2026
+930
−0
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
|
|
||
| # Sample Agent + Email Integration (Business Central) | ||
|
|
||
| This AL extension is a small **sample** showing how to connect **Agents** with **Email** in Microsoft Dynamics 365 Business Central. | ||
|
|
||
| It uses a recurring **scheduled task** to: | ||
|
|
||
| 1. Retrieve inbound emails from a configured email account. | ||
| 2. Create (or append to) **Agent Tasks** based on the email conversation. | ||
| 3. Send email replies for agent outputs that have been **Reviewed**. | ||
|
|
||
| ## How it works | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant TS as TaskScheduler | ||
| participant D as Sample Dispatcher | ||
| participant A as Agent | ||
| participant RE as Sample Retrieve Emails | ||
| participant E as Email | ||
| participant ATS as Agent Task | ||
| participant SR as Sample Send Replies | ||
| participant EM as Email Message | ||
|
|
||
| TS->>D: Run | ||
| D->>A: IsActive(Agent) | ||
| alt ShouldRun = false (Inactive) | ||
| D-->>TS: Exit | ||
| else ShouldRun = true | ||
| D->>RE: Retrieve emails | ||
| RE->>E: Retrieve emails | ||
| loop For each retrieved Email Inbox record | ||
| RE->>ATS: TaskExists(Conversation Id)? | ||
| alt Existing task | ||
| RE->>ATS: Append Agent Task Message | ||
| Note over RE,ATS: External ID = External Message Id | ||
| else New task | ||
| RE->>ATS: Create Agent Task + initial message | ||
| Note over RE,ATS: External ID = Conversation Id | ||
| end | ||
| RE->>E: Mark email as read | ||
| end | ||
|
|
||
| D->>SR: SendEmailReplies(Setup) | ||
| loop For each reviewed output message | ||
| SR->>ATS: Get external message id | ||
| SR->>EM: CreateReply | ||
| SR->>E: Reply | ||
| SR->>ATS: Mark output message as Sent | ||
| end | ||
|
|
||
| D->>TS: Create task | ||
| D->>ATS: Update Last Sync At if retrevial success | ||
| end | ||
| ``` | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant TS as TaskScheduler | ||
| participant EH as Sample Error Handler | ||
| participant A as Agent | ||
|
|
||
| TS->>EH: Run | ||
| EH->>A: IsActive(Agent) | ||
| alt ShouldRun = false | ||
| EH-->>TS: Exit | ||
| else ShouldRun = true | ||
| EH->>TS: Create task | ||
| end | ||
| ``` | ||
|
|
||
| An “agent cycle” is executed by the scheduled task running codeunit `50100 "Sample Dispatcher"`: | ||
|
|
||
| 1. **ShouldRun check** | ||
| - `Sample Dispatcher` calls `Agent.IsActive(Setup."Agent User Security ID")` to decide whether to run. | ||
| 2. **Retrieve emails → Agent tasks** | ||
| - `50102 "Sample Retrieve Emails"` calls `Email.RetrieveEmails(...)`. | ||
| - For each email in `Email Inbox`: | ||
| - If an agent task already exists for the email **conversation**, the message is appended to the existing task. | ||
| - Otherwise, a new agent task is created. | ||
| - The email is marked as read via `Email.MarkAsRead(...)`. | ||
| 3. **Send replies** | ||
| - `50103 "Sample Send Replies"` finds agent task output messages with: | ||
| - `Type = Output` | ||
| - `Status = Reviewed` | ||
| - `Agent User Security ID = Setup."Agent User Security ID"` | ||
| - It creates a reply-all email (`Email Message.CreateReplyAll`) and sends it with `Email.ReplyAll(...)`. | ||
| - On success, the agent output message is marked as sent. | ||
| 4. **Reschedule** | ||
| - `Sample Dispatcher` schedules itself again using `TaskScheduler.CreateTask(...)`. | ||
| - The sample currently reschedules with a fixed delay of **1 minute**. | ||
|
|
||
| If the dispatcher fails, `50101 "Sample Error Handler"` is used as the task error handler and will reschedule the next run. | ||
|
|
||
| ## Included objects | ||
|
|
||
| - `table 50100 "Sample Setup"` | ||
| - Stores which agent and email account to use, sync timestamps, and scheduled task IDs. | ||
| - `codeunit 50100 "Sample Dispatcher"` | ||
| - Orchestrates one cycle (retrieve emails, send replies, reschedule). | ||
| - `codeunit 50101 "Sample Error Handler"` | ||
| - Reschedules the dispatcher if the scheduled task run fails. | ||
| - `codeunit 50102 "Sample Retrieve Emails"` | ||
| - Retrieves inbound emails and creates/appends agent task messages. | ||
| - `codeunit 50103 "Sample Send Replies"` | ||
| - Sends reply-all emails for reviewed agent outputs. | ||
|
|
||
| ## Setup (what you need to configure) | ||
|
|
||
| This repo focuses on the integration logic and **does not include a setup page**. | ||
| To run the sample you must create a `Sample Setup` record (`table 50100`) in your own way (for example: add a small page in a companion extension, insert the record from a one-off helper codeunit, or create it during development/debugging). | ||
|
|
||
| Populate: | ||
|
|
||
| - `Agent User Security ID` | ||
| - The user security ID of the agent user. | ||
| - `Email Account ID` | ||
| - The email account to retrieve from and reply with. | ||
| - `Email Connector` | ||
| - The connector type used by the email account. | ||
|
|
||
| These are used behind the scenes for tracking sychronizations: | ||
|
|
||
| - `Earliest Sync At` | ||
| - Used as the lower bound when retrieving emails. | ||
| - `Last Sync At` | ||
| - Updated after a successful dispatcher run. | ||
|
|
||
| ## Important note: scheduled task user + email access | ||
|
|
||
| Business Central scheduled tasks run under a **user context** (the user who scheduled/created the task). | ||
|
|
||
| That means: | ||
|
|
||
| - The user who schedules the dispatcher must have permission to run this extension’s objects. | ||
| - The user must have access to the selected email account/connector. | ||
| If the user does not have access, email retrieval/reply operations can fail. | ||
|
|
||
| ## Notes / limitations (by design for a sample) | ||
|
|
||
| - The dispatcher’s “should run” logic is currently minimal (it only checks `Agent.IsActive(...)`). | ||
| - Email retrieval uses a simple filter: unread only, attachments loaded, last message only, earliest email = `Earliest Sync At`, max 50. | ||
| - Attachment MIME filtering is marked as TODO in `Sample Retrieve Emails`. | ||
|
|
||
| ## Quick start (developer flow) | ||
|
|
||
| 1. Publish the extension. | ||
| 2. Configure Email in Business Central (email account + connector). | ||
| 3. Schedule with the Schedule Sync action. | ||
| 4. Send an email to the email account selected. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| { | ||
| "id": "db9ab3df-d57d-47fb-8870-f7617f9341aa", | ||
| "name": "Sample Agent Email Integration", | ||
| "publisher": "Microsoft", | ||
| "version": "1.0.0.0", | ||
| "brief": "", | ||
| "description": "", | ||
| "privacyStatement": "", | ||
| "EULA": "", | ||
| "help": "", | ||
| "url": "", | ||
| "logo": "", | ||
| "dependencies": [ | ||
| { | ||
| "id": "00155c68-8cdd-4d60-a451-2034ad094223", | ||
| "name": "Business Central Agent Playground", | ||
| "publisher": "Microsoft", | ||
| "version": "27.2.0.0" | ||
| } | ||
| ], | ||
| "screenshots": [], | ||
| "platform": "1.0.0.0", | ||
| "application": "27.0.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be aligned with dependency on the AI development toolkit |
||
| "idRanges": [ | ||
| { | ||
| "from": 50100, | ||
| "to": 50149 | ||
| } | ||
| ], | ||
| "resourceExposurePolicy": { | ||
| "allowDebugging": true, | ||
| "allowDownloadingSource": true, | ||
| "includeSourceInSymbolFile": true | ||
| }, | ||
| "runtime": "16.0", | ||
| "features": [ | ||
| "NoImplicitWith" | ||
| ], | ||
| "target":"Cloud" | ||
| } | ||
76 changes: 76 additions & 0 deletions
76
samples/BCAgents/AgentEmailIntegration/src/Sample.Codeunit.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.Agent.Sample; | ||
|
|
||
| using System.Agents; | ||
|
|
||
| codeunit 50104 "Sample" | ||
| { | ||
|
|
||
| Access = Internal; | ||
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
|
|
||
| /// <summary> | ||
| /// Determines whether the scheduled agent task should run for the given setup. | ||
| /// </summary> | ||
| /// <param name="Setup">Setup record.</param> | ||
| /// <returns>True if the agent is active; otherwise false.</returns> | ||
| procedure ShouldRun(Setup: Record "Sample Setup"): Boolean | ||
| var | ||
| Agent: Codeunit Agent; | ||
| begin | ||
| // TODO: Implement validation logic to determine if the agent should run | ||
| if not Agent.IsActive(Setup."Agent User Security ID") then | ||
| exit(false); | ||
|
|
||
| exit(true); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Schedules the next dispatcher execution. | ||
| /// </summary> | ||
| /// <param name="Setup">Setup record.</param> | ||
| procedure ScheduleNextRun(Setup: Record "Sample Setup") | ||
| begin | ||
| // Ensure no other process can change the setup while we are scheduling the task | ||
| Setup.LockTable(); | ||
| // Ensure we have the latest record before modifying | ||
| Setup.GetBySystemId(Setup.SystemId); | ||
|
|
||
| // Remove existing scheduled task if any before rescheduling | ||
| RemoveScheduledTask(Setup); | ||
|
|
||
| Setup."Scheduled Task ID" := TaskScheduler.CreateTask(Codeunit::"Sample Dispatcher", Codeunit::"Sample Error Handler", true, CompanyName(), CurrentDateTime() + ScheduleDelay(), Setup.RecordId); | ||
|
|
||
| Setup.Modify(); | ||
| Commit(); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Cancels any previously scheduled tasks and clears task IDs on the setup record. | ||
| /// </summary> | ||
| /// <param name="Setup">Setup record containing scheduled task IDs.</param> | ||
| procedure RemoveScheduledTask(Setup: Record "Sample Setup") | ||
| var | ||
| NullGuid: Guid; | ||
| begin | ||
| if TaskScheduler.TaskExists(Setup."Scheduled Task ID") then | ||
| TaskScheduler.CancelTask(Setup."Scheduled Task ID"); | ||
|
|
||
| Setup."Scheduled Task ID" := NullGuid; | ||
| Setup.Modify(); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Returns the delay (in milliseconds) used when scheduling the next run. | ||
| /// </summary> | ||
| /// <returns>Delay in milliseconds.</returns> | ||
| local procedure ScheduleDelay(): Integer | ||
| begin | ||
| exit(2 * 60 * 1000); // 2 minutes | ||
| end; | ||
| } |
63 changes: 63 additions & 0 deletions
63
samples/BCAgents/AgentEmailIntegration/src/SampleDispatcher.Codeunit.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.Agent.Sample; | ||
|
|
||
| using System.Agents; | ||
|
|
||
| codeunit 50100 "Sample Dispatcher" | ||
| { | ||
|
|
||
| Access = Internal; | ||
| TableNo = "Sample Setup"; | ||
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
|
|
||
| trigger OnRun() | ||
| begin | ||
| RunSampleAgent(Rec); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Executes one agent cycle: retrieves emails, sends replies, and schedules the next run. | ||
| /// </summary> | ||
| /// <param name="Setup">Agent setup record.</param> | ||
| procedure RunSampleAgent(Setup: Record "Sample Setup") | ||
| var | ||
| Sample: Codeunit "Sample"; | ||
| LastSync: DateTime; | ||
| RetrievalSuccess: Boolean; | ||
| begin | ||
| // Validate task should still run | ||
| if not Sample.ShouldRun(Setup) then | ||
| exit; | ||
|
|
||
| LastSync := CurrentDateTime(); | ||
|
|
||
| // Sync emails | ||
| RetrievalSuccess := Codeunit.Run(Codeunit::"Sample Retrieve Emails", Setup); | ||
| Codeunit.Run(Codeunit::"Sample Send Replies", Setup); | ||
|
|
||
| // Reschedule next run | ||
| Sample.ScheduleNextRun(Setup); | ||
|
|
||
| if RetrievalSuccess then | ||
| UpdateLastSync(Setup, LastSync); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Persists the last successful synchronization datetime on the setup record. | ||
| /// </summary> | ||
| /// <param name="Setup">Setup record to update.</param> | ||
| /// <param name="LastSync">Datetime to store as the last sync time.</param> | ||
| local procedure UpdateLastSync(var Setup: Record "Sample Setup"; LastSync: DateTime) | ||
| begin | ||
| Setup.GetBySystemId(Setup.SystemId); | ||
| Setup."Last Sync At" := LastSync; | ||
| Setup.Modify(); | ||
| Commit(); | ||
| end; | ||
|
|
||
| } |
36 changes: 36 additions & 0 deletions
36
samples/BCAgents/AgentEmailIntegration/src/SampleErrorHandler.Codeunit.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.Agent.Sample; | ||
|
|
||
| codeunit 50101 "Sample Error Handler" | ||
| { | ||
|
|
||
| Access = Internal; | ||
| TableNo = "Sample Setup"; | ||
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
|
|
||
| trigger OnRun() | ||
| begin | ||
| RunSampleAgent(Rec); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Handles dispatcher failures by rescheduling the next run. | ||
| /// </summary> | ||
| /// <param name="Setup">Agent setup record.</param> | ||
| procedure RunSampleAgent(Setup: Record "Sample Setup") | ||
| var | ||
| Sample: Codeunit "Sample"; | ||
| begin | ||
| // Validate task should still run | ||
| if not Sample.ShouldRun(Setup) then | ||
| exit; | ||
|
|
||
| // Reschedule run | ||
| Sample.ScheduleNextRun(Setup); | ||
| end; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app name has changed. You should also depend on the AI developement toolkit and not on this appId.