Zeit365 is not yet available. We are in active development.

Building a modern SaaS with Microsoft 365 integration

An opinionated guide to building a modern SaaS with deep Microsoft 365 integration. By using the tools, services and data from Microsoft 365, we can create something really cool.

Introduction

Hey there! šŸ‘‹šŸ»

My name is Dominic Fuchs and I’m the Lead Product Developer at AURUM. We are a Microsoft Service Provider since 2014 from Nuremberg, Germany. Our team consists of really awesome Microsoft Experts, so don’t hesitate to Contact Us on any topic you want.

AURUM’s Product Team was established in June 2024 and we have in fact already shipped a product. It’s called AURUM Navigation. It’s a SharePoint WebPart which makes building Grid Layouts simpler. Developing and shipping this product taught us a lot. Mainly: We want to go beyond SharePoint.

We decided to build a SaaS product.

In this guide I will share things we learned and how we build something really useful.

I’m proud to present to you an opinionated guide on ā€œBuilding a modern SaaS with Microsoft 365 Integrationā€. šŸŽ‰

Setting the Stage

Before we start things off, we need to define what we want to build and what our resources look like.

The product I will use as an example for this guide is Zeit365. A product that our team is currently working on and will have an Alpha Launch this year! Even though we are pre-alpha, feel free to Join the Waitlist.

First of all, it’s really important to assess our resources. Without knowing our resources, we cannot set expectations and the scope of our SaaS product.

Our Resources:

  • 🫄 Small team
  • šŸ“† Time until End of the Year
  • šŸ’› Microsoft Gold Partnership
  • 🄸 In-Company Consultations of Experts
  • ā˜ļø Business Network to ask for Feedback

Now the stage is set. Time to dive in.

Tenant-First Approach

Let’s face the current reality. There is a lot of digital data in our world. The decisions on where data is stored, how it is handled, and what it is used for are often questions that a private person doesn’t ask. I mean… Do you know in which country your Instagram profile picture is stored? In our private life, we might care more or less, but that’s up to each individual’s priorities.

From the perspective of a business owner or a service provider, it’s an absolute must to be informed about the handling of data. It might be a challenge, but we are responsible for the data our customers or co-workers produce.

In Microsoft 365, your business data is scoped to your ā€œtenantā€ by default. That means that every user you create, every SharePoint site you manage, and every note you take in OneNote live isolated and protected within your Microsoft 365 tenant. Usually, this data is located in a data center near your business residence. You have full control over who has the right to access what, and in what capacity.

ā€Tenant-First Approachā€ means that anything that can be provided or managed by your Microsoft 365 tenant should be provided or managed by your tenant.

Data Handling

Now that we have learned about the Tenant-First Approach, we can put this key principle into action.

Integrate Microsoft Graph

Microsoft Graph allows users of our app to interact with the data of their Microsoft 365 tenant. Not only that, it does so on behalf of the user. That means that all permissions of the user are respected and our app cannot access your tenant’s data on its own.

You can learn more about authentication on behalf of users in the official Microsoft documentation.

That’s great and all, but we still need to decide which data we want to integrate. A good starting point is to first determine which entities our app needs, look at the Microsoft Graph docs and find matches:

As you can see most of the entities we need are already covered by Microsoft Graph.

We still need to make informed decisions on which entities to use from Microsoft Graph and which to build ourselves. There are entities, like Contact for example, that might not suit our needs.

Don’t save what already exists

I think this might be the easiest to explain by using an example.

Zeit365 allows you to manage your business customers. The app needs to show the name and the email address of the user that created a customer, so co-workers know who might be responsible for it.

titlecreatorUserId
1Unbreakable Windows98f0fb88-3584-11f0-ae57-0f37e01e966f
2YumYum Coffee1fe3bd6a-3585-11f0-a823-2318f177c89c
3Awesome Cars207a552c-3585-11f0-ab04-131ff5b8151c

Have a look at this simplified sample of some database entries. As you can see, there is no information on the user’s name or the email address. We only save the creatorUserId of your tenant’s user. The app takes this ID and resolves it for the needed information.

That brings some key advantages:

  • There is one source of truth: You don’t need to update the user’s data within the app if the tenant’s user data changes.
  • Permissions remain intact: Your data can be protected by using the permission system of your tenant.
  • Being prepared for the worst case: The user’s personal information is not exposed in the event of a data breach. The easiest way to protect data is to not save data.
By using this approach for all your data, the data our app needs to save by itself becomes minimal.

Ensure Data Ownership

Data ownership goes further than knowing where your data is stored.

We are handling backups, recovery, and encryption for you - that doesn’t mean you shouldn’t be able to fully own your data.

The easiest way for this is to give you full access to your database.

Database

Okay. Let’s dive into the decision-making process by picking the centerpiece of our app.

We have to define our database requirements from the perspective of a SaaS provider first:

  • Full database access: Every tenant needs to be able to export/import its database.
  • Cheap horizontal scaling: We want to provide a separate database for each tenant without passing costs.
  • Isolated runtime: Each database needs to run fully independent of another, so problems can be addressed in isolation.
  • Easy long-term maintenance: We are a small team with limited resources and our app needs to be maintained for a long period of time.
I don’t have to mention that a database also needs to be fast and secure. 🄸

🄁 And the winner is 🄁

SQLite

It’s just a file

The entire database is just one file.

We can create a database by creating an empty file. We can move a database by moving the file. We can back up a database by copying the file. I think you see the pattern.

By being just a file, we can scale with almost zero cost, provide you the option to download your database and make backups without downtime.

That’s awesome, but there is more to the portability of a database—namely, distribution. But this is a topic for another blog post, so stay tuned and follow us on LinkedIn to not miss it.

SQLite is done

To best explain what I mean by saying ā€œSQLite is doneā€, I will tell you a very short real-life story of mine.

Back in 2011, I worked at a software manufacturer handling loads of very sensitive data within an Oracle database. Our team was faced with the impossible task of updating the version of Oracle. With thousands of functions, procedures, and hundreds of tables—we knew… we were in trouble. The app’s development progress almost stopped entirely for about 2 weeks.

What I want to emphasize with this story is how important it is to pick something ā€œstableā€ that will most likely not change in a big way.

SQLite is rather perfect in that regard. The latest major version of the SQLite standard was released in 2004. By being ā€œjust a file formatā€, the client application is responsible for interpreting SQL statements, running operations, or enforcing constraints.

An old SQLite database will still work in a modern environment without the need to update any database software or be limited to using an old client library.

Performance

SQLite is incredibly fast. Looking at benchmarks and comparing SQLite to any other relational database, you will be surprised that it beats all the traditional ones like Postgres or MySQL.

Accessing a file directly is much faster than sending data to a different process, using the networking layer, letting it be processed by a Database Management System, waiting for aggregations or mutations, sending it back, transforming da… You get the point.

There are limitations, such as the maximum amount of data being ā€œsmallerā€ (still billions of rows), and the computing cap is limited by your app’s resources. Luckily, that doesn’t matter in our infrastructure because data interaction is distributed by tenant. One database only needs to serve one tenant.

Great developer experience

I love that topic. ā™„ļø

Drizzle enables you to define your schema, the shape of your database tables, columns, indexes, and so on, within your code and provides you a type-safe client to interact with your data.

import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";

export default sqliteTable(
    "customer",
    {
        id: integer().primaryKey(),
        title: text().notNull(),
        description: text(),
        slug: text().notNull().unique(),
        msGroupId: text().notNull(),

        likesCats: integer({ mode: "boolean" }).default(true), // just add a column
    },
    () => [],
);

Drizzle reflects changes to your schema as incremental database migrations. It creates, applies, and manages those database migrations, and ensures your database always matches your code base - even if you start with a fresh and empty database.

The combination of an ORM like Drizzle and a file-based database like SQLite enables developers to move fast. Just create a local empty file, push your latest schema to it, and start developing.

Infrastructure

Use Azure

Azure provides us with scalable hosting options, a reliable and secure environment, and tools that just work with Microsoft 365 nicely.

Looking back at our Resources, we can get a lot of knowledge and support from within our own company.

Run in Isolation

The way we are hosting your app, again, follows the Tenant-First Approach too.

Look at the way Software as a Service apps usually work: A user requests the app, the app uses load balancing, the user’s geolocation and other metrics to decide which server instance handles the request. This explanation is intentionally simplified, because it would be way too complicated to explain everything that goes into an architecture like that.

Now let’s look at the way our SaaS architecture, using the Tenant-First Approach, works: A user requests the app of its tenant, which is running on a dedicated App Service in Azure. Because we’re dealing with a predictable amount of users, we will simply scale the performance of the App Service to your needs.

Each of our customers will get an exclusive App Service Instance.

This has a lot of advantages:

  • Simplifies the development: Building an app that serves a finite amount of people is much easier than building an app that needs to scale for an infinite number of users.
  • You stay in control of updates: You have the option to apply or skip an update whenever you feel like.
  • Performance metrics scoped by default: Each performance metric is scoped by default. That way we can ensure your app always has enough resources to run at peak performance.
  • Support gets much simpler: By limiting the amount of potential problems, we can help you out way quicker.
  • Staying flexible: We are able to change the database, the Azure App Registration, or the machine your app runs on without risking to break the runtime of another tenant.

Make Self-Hosting an Option

  • Your data is handled with respect… 🫔
  • You are in control of your database… šŸ§‘šŸ¼ā€šŸ’»
  • Your app runs in an isolated runtime… šŸ”’

… So why do we offer Self-Hosting?

Even though we lean heavily on Trust, there are business cases in which Trust doesn’t matter and we need to respect that.

Let’s say that you are working for the government, trying to find a nice time tracking solution but there is a ā€œNo Third Partyā€ rule. Or you just want to put that recently bought server rig to the test… I can feel that.

There are so many situations in which Self-Hosting might be your only option.

Zeit365 can run almost anywhere. It doesn’t even have to be Azure.

Monetization

This part is not about how to make a lot of money. It’s about how a SaaS can be monetized and how Payment Processing works.

We all know payment providers like Stripe, PayPal, and Klarna. But did you know that Microsoft can work as a Payment Provider too?

You can monetize your SaaS product by using Microsoft’s ISV program. ISV stands for Independent Software Vendor.

Finally, our Microsoft Partnership comes into play. As a Microsoft Partner, we can offer our SaaS product within the Microsoft App Store. Microsoft handles all payments, creates invoices for you, and best of all:

You can assign your bought licenses to your users from within your own Microsoft Admin Center.

Assign Zeit365 licenses from within your admin center

Thanks for Reading

Using the Tenant-First Approach as a key principle, we can point our decision-making in a customer-friendly direction.

You may think ā€œWait! What tools do you use to build your app? How about automations, deployment, or backup strategies?ā€ I want to dive deep into all those topics in the future. Follow us on LinkedIn if you don’t want to miss out on that.

Consider Joining the Waitlist to get Alpha Access to Zeit365 early on!

I hope to see you soon! 😊

Dominic Fuchs

Dominic Fuchs šŸš€ Building Zeit365

Hi! I'm the main maintainer of Zeit365! I'm passionate about Microsoft technologies, JavaScript and building things. I really like networking and talking about tech, so if you have questions or ideas, feel free to reach out via email. šŸ‘‹šŸ»

Send me a message āœŒšŸ»