Microsoft 365 APIs and Python Tutorial

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.

  • Skip the theory, and go straight into the implementation, that's the main theme.
  • No need to implement any OAuth2 authentication or to use Graph APIs. All the details of this sort will be taken care of automatically for you.

How This Tutorial Works

  • Built for practitioners - no marketing fluff, just practical techniques for Microsoft 365 automation.
  • Implementation-first approach - starts with working code and explains the necessary concepts as they arise.
  • Production-ready results in 60 minutes - practical solutions you can deploy today.

Learning objectives

  • How to connect to Microsoft 365 APIs in Python with minimal effort, and without any struggles with OAuth2 or Graph APIs.
  • How to automate Calendar, SharePoint and Email in particular.
  • How to conveniently schedule jobs and tasks that will run your automations in background, without any programming required.

Most enterprise automation tasks involve coordinating multiple Microsoft 365 services. Here's what this typically looks like in production:

  • Sending emails or reading data from Outlook mailboxes
  • Cross-referencing calendar availability
  • Creating calendar events based on email content
  • Fetching additional data from SharePoint
  • Checking users in Entra ID (Azure AD)
  • Updating multiple systems in sequence

While we focus on Outlook, Calendar, and SharePoint examples, the same patterns apply to any Microsoft 365 API.

Prerequisites and Setting Up

Docker logo

  • 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.

Getting Your Microsoft 365 App Credentials

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:

  • Tenant ID (e.g. acme.example.com or a1898248-b908-4761-974a-94472ad8083f)
  • Client ID: (e.g. d35a0e1a-d389-4475-b37a-366700ebead9)
  • Client secret: (password for the app)
  • Email address: (needed to send and read emails)

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.

Create a Microsoft 365 Cloud API Connection

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.

Create a Microsoft 365 IMAP Email Connection

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.

Microsoft 365 Outlook Email in Python

# -*- 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.

Creating a New Service and Invoking It

  • 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.

Implementing Authentication in Python for Microsoft 365

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.

What is a Service?

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.

Microsoft 365 IMAP Email in Python

# -*- 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.

Microsoft 365 Calendar in Python

# -*- 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.

Task Scheduling in Python

  • 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.

Diving Deeper - Using Graph REST APIs Directly

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}')

Bonus Points - Entra ID (Azure AD) in Python

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

Finding More Usage Examples

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

  • AddressBook
  • Excel
  • Directory and Users
  • MailBox
  • OneDrive
  • Outlook Categories
  • SharePoint
  • Tasks

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.

What We've Learned

Throughout this guide, we've explored how to build production-ready Microsoft 365 automations using Python and Zato. Let's recap the key points:

  • Services are Python classes that encapsulate specific business functionality
  • Each service maintains a clean separation between business logic and technical infrastructure
  • Connection management and authentication are handled transparently by the platform

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.

Continue Your API Learning Journey