This document defines the interaction interface between the OSEM (Outlook Event Monitor) plugin and external Python scripts.
The plugin executes scripts by launching the Python interpreter via the Windows command line.
- Execution Command:
python.exe "<ScriptPath>" "<ContextJsonPath>" - Working Directory: The directory where the script resides.
- Arguments:
ScriptPath: The absolute path to the Python script.ContextJsonPath: The absolute path to a temporary JSON file containing the current event context information.
The temporary JSON file generated by the plugin uses UTF-8 encoding. The root object contains the following fields:
| Field Name | Type | Description |
|---|---|---|
EventId |
string | Unique identifier for the event (GUID). |
EventTitle |
string | Title of the event (usually the email subject or a custom title). |
DashboardTemplateId |
string? | ID of the currently used dashboard template (can be null). |
DashboardValues |
object | Dictionary of key-value pairs containing existing dashboard field values. |
Emails |
array | List of related emails (sorted by time descending or relevance). |
Attachments |
array | List of related attachments. |
| Field Name | Type | Description |
|---|---|---|
EntryId |
string | Unique EntryID of the Outlook mail object (Key field for COM calls). |
StoreId |
string | ID of the store where the email resides. |
Subject |
string | Email subject. |
Sender |
string | Sender address/name. |
Participants |
array[string] | List of all participants (To, CC). |
ReceivedOn |
string | Received time (ISO 8601 format). |
BodyFingerprint |
string | Email body fingerprint (for deduplication). |
InternetMessageId |
string | Internet Message ID of the email. |
ConversationId |
string | Conversation ID. |
| Field Name | Type | Description |
|---|---|---|
Id |
string | Unique identifier for the attachment. |
FileName |
string | File name (including extension). |
FileType |
string | File extension (e.g., .pdf). |
FileSizeBytes |
number | File size (bytes). |
SourceMailEntryId |
string | EntryID of the email to which this attachment belongs. |
{
"EventId": "550e8400-e29b-41d4-a716-446655440000",
"EventTitle": "Shipment #12345 Update",
"DashboardTemplateId": "logistics_template_v1",
"DashboardValues": {
"HAWB": "H123456789",
"Status": "In Transit"
},
"Emails": [
{
"EntryId": "000000001A4473...",
"Subject": "Re: Shipment #12345",
"Sender": "agent@logistics.com",
"ReceivedOn": "2023-10-27T10:30:00"
}
],
"Attachments": [
{
"FileName": "Invoice.pdf",
"SourceMailEntryId": "000000001A4473..."
}
]
}When a script is configured as a "Global Script" and executed from the main interface, the structure of the input data differs.
Check the IsGlobalExecution field within DashboardValues to determine if it is running in global execution mode.
{
"EventId": "GLOBAL",
"EventTitle": "Global Execution",
"DashboardValues": {
"IsGlobalExecution": "true",
"GlobalDataPath": "C:\\Users\\User\\AppData\\Local\\Temp\\osem-global-data-xxxx.json"
},
"Emails": [],
"Attachments": []
}In global mode, the actual list of events is stored in another JSON file pointed to by GlobalDataPath. This file contains an array of EventRecord objects.
Important Note: The global data file directly serializes the C# EventRecord object, so its structure is slightly different from the Context in single-event mode and contains more complete information.
Global Data JSON Structure (Array of EventRecord):
[
{
"EventId": "550e8400-...",
"EventTitle": "Shipment #12345",
"Status": "Open",
"DashboardTemplateId": "logistics_v1",
"CreatedOn": "2023-10-01T10:00:00Z",
"LastUpdatedOn": "2023-10-05T14:30:00Z",
// Note: This is a DashboardItems array, not a DashboardValues dictionary
"DashboardItems": [
{ "Key": "HAWB", "Value": "H123", "Confidence": 1.0 },
{ "Key": "ETD", "Value": "2023-11-01", "Confidence": 0.8 }
],
"Emails": [
{ "EntryId": "...", "Subject": "...", "Sender": "...", "ReceivedOn": "..." }
],
"Attachments": [
{ "FileName": "Inv.pdf", "SourceMailEntryId": "..." }
],
// Extra fields
"ConversationIds": [ "..." ],
"RelatedSubjects": [ "..." ],
"Participants": [ "..." ]
},
...
]import sys
import json
import os
def get_dashboard_value(event, key):
"""Helper to get value from EventRecord structure"""
for item in event.get("DashboardItems", []):
if item.get("Key") == key:
return item.get("Value")
return None
def load_data():
# 1. Read basic Context
context_path = sys.argv[1]
with open(context_path, 'r', encoding='utf-8') as f:
context = json.load(f)
# 2. Check if it is Global Mode
dashboard_values = context.get("DashboardValues", {})
if dashboard_values.get("IsGlobalExecution") == "true":
# 3. Read global data file
global_data_path = dashboard_values.get("GlobalDataPath")
if global_data_path and os.path.exists(global_data_path):
with open(global_data_path, 'r', encoding='utf-8') as df:
all_events = json.load(df)
return "GLOBAL", all_events
# Normal Single Event Mode
return "SINGLE", context
def main():
mode, data = load_data()
if mode == "GLOBAL":
print(f"Processing {len(data)} events in GLOBAL mode.")
for event in data:
# Access full data
title = event.get('EventTitle')
hawb = get_dashboard_value(event, 'HAWB')
email_count = len(event.get('Emails', []))
print(f" - [{hawb}] {title} ({email_count} emails)")
else:
# Single Event Mode (Context Structure)
print(f"Processing single event: {data.get('EventTitle')}")
# In single event mode, access DashboardValues dictionary directly
vals = data.get('DashboardValues', {})
print(f" - HAWB: {vals.get('HAWB')}")
if __name__ == "__main__":
main()Scripts should read the file path passed via command line arguments and parse the JSON content.
import sys
import json
def load_context():
if len(sys.argv) < 2:
raise ValueError("Missing context file path argument")
context_path = sys.argv[1]
with open(context_path, 'r', encoding='utf-8') as f:
return json.load(f)If the metadata in JSON is insufficient (e.g., need to read email body or download attachments), you can use the pywin32 library to connect to Outlook via EntryId.
import win32com.client
def get_mail_body(entry_id):
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
item = outlook.GetItemFromID(entry_id)
return item.BodySince attachment content is not directly included in the JSON, you need to find the original email via SourceMailEntryId and download the attachment.
import os
def save_attachments(context, output_dir):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
# Note: If in global mode, context is a list, requiring a double loop
# Here assuming context is a single event object
for att_info in context.get("Attachments", []):
entry_id = att_info.get("SourceMailEntryId")
file_name = att_info.get("FileName")
try:
mail = outlook.GetItemFromID(entry_id)
for attachment in mail.Attachments:
if attachment.FileName == file_name:
# SaveAsFile requires an absolute path
save_path = os.path.abspath(os.path.join(output_dir, file_name))
attachment.SaveAsFile(save_path)
break
except Exception as e:
# Recommended to print logs to stderr to avoid interfering with stdout JSON output
sys.stderr.write(f"Error saving {file_name}: {e}\n")Scripts should print the processing results to standard output (stdout) in JSON format. Recommendation: Print the JSON result only on the last line, and print other log information to standard error (stderr).
Recommended Output Structure: The plugin should be designed to receive update instructions in the following format (requires plugin-side code support):
{
"updates": {
"DashboardValues": {
"ETD": "2023-11-01",
"Vessel": "EVER GIVEN"
}
},
"logs": [
"Extracted ETD successfully",
"Confidence score: 0.98"
]
}0: Execution successful.Non-0: Execution failed (the plugin may capture this state and report an error).
- Encoding: Always use UTF-8 encoding when reading/writing files and processing strings to avoid character encoding issues (especially with non-ASCII characters).
- Dependencies: Minimize third-party library dependencies, or ensure the runtime environment has the required libraries installed (e.g.,
pywin32,ollama, etc.). - Exception Handling: Scripts should include global exception handling to print error information to stderr or include it in a returned JSON error field, preventing silent failures.