Blog
Here's how to build Python-based Microsoft 365 automations that actually work in production. This guide focuses on battle-tested patterns that solve real business problems, based on real implementation experience building enterprise Microsoft integrations.
Most enterprise automation tasks involve coordinating multiple Microsoft 365 services. Here's what this typically looks like in production:
While we focus on Outlook, Calendar, and SharePoint examples, the same patterns apply to any Microsoft 365 API.
Install Zato and confirm you can reach the dashboard at http://localhost:8183 (username: admin, password: the same you chose during the installation).
Though Zato provides a VS Code plugin, we'll begin with its built-in IDE. Once you're in the dashboard, navigate to Services → IDE
to access it. This will get you started quickly, even if you plan to use VS Code later.
Ask your admin, or do it yourself if you have permissions, to create a new Microsoft 365 app.
Once the app is created, you'll then need the following:
We can now create connection that to your organization's Microsoft 365 account, and we're going to create two of them: one for general Microsoft 365 APIs, and one for reading email specifically.
In the dashboard, go to Vendors → Microsoft → 365 → Cloud
and fill out the form as below, copy-pasting the client details of the app created for you by your admin.
In the dashboard, go to Vendors → Microsoft → 365 → IMAP
and fill out the form as below, making sure first that you select "Microsoft 365" for the server type. Afterwards, click "Change secret" and enter your client's secret.
Great, you're all set up, so we can write our automations now. Let's start with reading emails, which is a fundamental first step in most business integrations.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class SendEmail(Service):
def handle(self):
# Connection to use ..
conn_name = 'My MS 365 Connection'
# Email address to send messages from
email_from = 'sender@example.com'
# Email to send messages to
email_to = 'recipient@example.com'
# .. access the connection pool ..
ms365 = self.cloud.ms365[conn_name]
# .. obtain a connection ..
with ms365.conn.client() as client:
# ..use the mailbox API..
mailbox = client.impl.mailbox(resource=email_from)
# .. build our message ..
message = mailbox.new_message()
message.to.add(email_to)
message.subject = 'Testing'
message.body = '<b>Hello</b>'
# .. and send it out.
message.send()
Theory is important, but let's see the code in action first. Just like in any integration project, hands-on experience will make the concepts clearer when we discuss them later.
In the dashboard, go to Services → IDE
, then click File → New
, give the new file a name of "email.py", and then save it.
Now, paste the code into your new file and click Deploy
.
Like any integration service you'll write, you trigger it with Invoke
.
In a moment, you'll see the results in your inbox, sent directly from your dashboard.
One of the key benefits of an integration platform is that it handles all the complex authentication and security requirements behind the scenes. While OAuth2 tokens and Microsoft 365 APIs are crucial for security, they shouldn't be your concern at all. Your job is to focus on the Python logic that delivers business value.
Now, to understand how all this fits together, let's explore what a service really is and how it powers your integration architecture.
A service in Zato is a Python class that embodies a specific piece of business functionality. Each service has a handle
method that executes when the service is invoked, whether through the dashboard, a REST endpoint, the server's scheduler, or through one of many other endpoints.
What makes services powerful is their focused nature. Take our Microsoft 365 example - the service code doesn't concern itself with connection details or authentication. It simply executes its business logic, while Zato handles all platform-level responsibilities, including the complexities of Microsoft 365 connectivity.
This clean separation of concerns is what makes services so effective in integration scenarios. Your Python code remains focused on business value, independent of the underlying technical complexities.
Now that we understand these fundamentals, let's explore more practical examples of working with Microsoft 365 APIs.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class ReadEmails(Service):
def handle(self):
# Connection to use ..
conn_name = 'My MS 365 Connection'
# .. get a new connection to the email server ..
conn = self.email.imap.get(conn_name).conn
# .. go through each of the messages found ..
for _, msg in conn.get():
# .. log what we've found ..
self.logger.info(f'Message received: {msg.data}')
# .. and mark it as seen.
msg.mark_seen()
This service follows the same pattern as our email reader - it leverages a connection definition to access messages, processes them, and marks them as seen to prevent duplicate processing in subsequent runs.
The code iterates through each message, currently logging them for demonstration purposes, though in a production environment you'd typically extract and process their contents based on your business requirements.
A key point here is that the inbox being accessed is determined by the IMAP connection definition - in this case, user@example.com - which again demonstrates how services remain focused on business logic while connection details are managed at the platform level.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class UpdateCalendarEvents(Service):
def handle(self):
# Connection to use ..
conn_name = 'My MS 365 Connection'
# .. whose calendar we're going to access ..
email = 'user@example.com'
# .. we'll look up events between these dates ..
start_date = '2025-01-01'
end_date = '2025-12-31'
# .. how to order calendar events ..
order_by = 'start/dateTime desc'
# .. access the connection pool ..
ms365 = self.cloud.ms365[conn_name]
# .. obtain a connection ..
with ms365.conn.client() as client:
# ..use the Calendar API (aka "schedule") ..
schedule = client.impl.schedule(resource=email)
# .. get the account's default calendar ..
calendar = schedule.get_default_calendar()
#.. create query filters ..
query = calendar.new_query('start').greater(start_date).less(end_date)
# .. extract all the events ..
events = calendar.get_events(query=query, order_by=order_by, limit=10000)
# .. go through each ..
for event in events:
# .. log what we're doing ..
self.logger.info(f'Processing event: {event}')
# .. set a new location for the event ..
event.location = 'Building 123'
# .. and save it permanently.
event.save()
The calendar service follows the same pattern: a connection is established, events are read, but this time around we first print them, so we can see what we're processing, and then we update them all. Same pattern, different logic, yet the idea is the same.
But this brings us to the next subject - running services periodically, in background. So let's see how to defined background jobs in the scheduler now.
In the dashboard, go to Scheduler
, then click Create a new job → interval-based
and fill out the form as below:
While development benefits from rapid intervals to see immediate results, production environments typically run these services at longer intervals - minutes or hours, depending on your business needs.
This flexibility in scheduling demonstrates another key strength of service-oriented architecture: the same service can respond to both real-time REST requests and execute as a background task through scheduled jobs, giving you multiple ways to leverage your business logic.
Sometimes you'll want to access Microsoft 365's Graph endpoints directly - there's plenty of documentation and code samples out there that show REST and HTTP calls, and you'll want to implement them quickly in your Zato environment. The platform makes this straightforward while handling all the authentication complexities behind the scenes.
Using the same connection definitions and Python client, you can invoke any Microsoft 365 REST API through the client.impl.connection.get method
.
The platform continues to handle OAuth2 tokens transparently, letting you focus on implementing the API calls. our example uses SharePoint, this approach works uniformly across all Microsoft 365 REST APIs.
def handle(self) -> 'None':
# Our SharePoint site
site_id = '123'
# A SharePoint list we're interested in
list_id = '456'
# A Microsoft Graph API endpoint to invoke
endpoint = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{list_id}/columns"
# Connection name to use
conn_name = 'My MS 365 Connection'
# Get the connection object
conn = self.cloud.ms365.get(conn_name).conn
# Use a client that will connect to Microsoft 365 ..
with conn.client() as client:
# .. invoke the endpoint directly, using the Graph API ..
response = client.impl.connection.get(endpoint)
# .. extract a JSON response ..
data = response.json()
# .. and print it to logs.
self.logger.info(f'SharePoint data: {data}')
Having covered SharePoint and email integrations, let's turn to Entra ID (formerly Azure AD) user management.
By now, you'll recognize our integration pattern: we create a service, obtain a connection, and use the client to interact with Microsoft 365's APIs - keeping our code clean and focused on business functionality.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class EntraID(Service):
def handle(self):
# Response to produce
out = []
# Connection to use ..
conn_name = 'My MS 365 Connection'
# .. access the connection pool ..
ms365 = self.cloud.ms365[conn_name]
# .. obtain a connection ..
with ms365.conn.client() as client:
# .. point to the underlying API ..
api = client.api()
# .. we are accessing an Entra ID instance ..
directory = api.directory()
# .. now, get all users ..
users = directory.get_users()
# .. iterate through all of them ..
for user in users:
# .. append user data to the response that we are producing ..
out.append({
'mail': user.mail,
'full_name': user.full_name,
})
# .. and return the response to the caller.
self.response.payload = out
Under the hood, Zato uses the O365 Python library for Microsoft 365 connectivity. This gives you immediate access to the full spectrum of Microsoft 365 services
Any code example you find in the O365 documentation can be implemented directly in your Zato services, following the patterns we've explored in this guide. The integration platform takes care of the infrastructure, you focus on the business logic.
Throughout this guide, we've explored how to build production-ready Microsoft 365 automations using Python and Zato. Let's recap the key points:
The O365 library integration means you can extend these patterns to other Microsoft 365 services like AddressBook, Excel, OneDrive, and Tasks, while maintaining the same clean separation of concerns we've demonstrated.
This architecture provides a foundation for building complex, production-ready integrations that are both maintainable and scalable. By letting Zato handle the infrastructure complexities, you can focus on delivering business value through your Python code.