Power Platform

Matt  ยท  MattFlows

What building a regulated healthcare app on Power Platform actually taught me about where low-code platforms genuinely earn their keep, and when to set your preferences aside.

If you've read anything on this blog before, you know I'm an n8n and Python guy. It's my default orchestrator, and my first instinct on any automation project, and the tools I've built the most reps on. I think it's genuinely one of the best workflow platforms available right now for the kind of multi-system, data-heavy integrations that actually matter in the real world. A close second is make.com with Zapier coming in a respectable third place.

But I took on a project recently that I couldn't build in n8n, and I don't mean that as a knock on n8n. The client was in healthcare and the environment was Microsoft-native wall to wall. In fact the COO told me he misses the Windows phone. The regulatory requirements were strict, the audit and compliance story had to hold up, and the deadline wasn't realistic but I've been learning that I need to change my definition of realism. I could have made n8n work technically. Making it work inside the governance, security, and timeline constraints of this engagement was a different question, and the honest answer was no.

So I built it on Power Platform. And I want to walk through what that actually looked like, because there's a real conversation worth having about what it means to pick the right tool when the right tool isn't the one you'd normally reach for.

API or App Sync

The Semi Intelligent App I Was Building

The application is a medication status portal for healthcare workers. Practitioners and care staff needed a single place to look up a patient's medication status, review associated ticket information, and pull down relevant documents tied to that patient's case. Think prior authorizations, prescription records, approval documentation, that kind of thing. Nothing about the use case is exotic. The complexity came entirely from the environment it had to live in.

The authoritative data, the source of truth, lived on-premises in a SQL Server. Documents weren't stored directly on the records but linked through a relational table. Retrieving a document didn't mean reading from a file path or a blob store. It meant calling a backend REST API with specific identifiers, getting back a temporary expiring download URL, and surfacing that to the user inside a session. Healthcare regulations dictated that access had to be gated, auditable, and that data couldn't just flow freely between systems without appropriate controls in place.

On top of all of that, the timeline was a little crazy. This wasn't a build-it-right-from-scratch-over-six-months engagement. The client needed a working MVP fast, and the compliance requirements meant there wasn't room to figure out security and governance after the fact.

Why Power Platform and Not n8n

I think a lot of consultants let tool loyalty do their thinking for them. n8n is fantastic at orchestration, and it's the backbone of most of the lab I run my own automation work out of. If this client had a cloud-native environment, a looser security posture, and more runway on the build, I probably would have used it. The workflow flexibility, the cost profile, and my own familiarity with it would all have pointed that direction.

But this client was Microsoft native end to end. Their identity was in Azure AD. Their compliance framework was already built around Microsoft's security and governance tooling. The On-Premises Data Gateway, which is what you need to reach an on-premises SQL Server without punching firewall holes, is a first-class feature inside Power Automate. Running n8n in that same environment would have meant standing up separate infrastructure, re-solving identity, and rebuilding the compliance story from scratch.

I simply didn't have time to code this from the ground up either. The deadline was real. Power Platform gave me the connectors, the gateway support, the Dataverse binding, and the portal layer already built and ready to wire together. That's not a consolation prize. When the clock is running and the environment is already Microsoft native, Power Platform is genuinely the right call. Not because it's better than n8n in the abstract, but because it was the right fit for this client, this environment, and these constraints. That's the job.

The Architecture I Actually Landed On

My first pass at this was a live, on-demand query. Healthcare worker submits a search, that creates a record in Dataverse, the record creation triggers a flow, the flow goes out through the gateway, hits SQL, comes back, writes the results to the record, and the portal renders it. Clean on paper. The problem showed up the moment I started testing it under anything resembling real load.

Every single search was a full round trip through the On-Premises Data Gateway in real time. The gateway is solid, but it's not built to be hammered with a live query on every form submission from a portal full of healthcare workers searching throughout the day. Latency stacked up, and worse, it tied the user's wait time directly to however the on-prem SQL Server happened to be performing at that exact second. That's not a foundation you want under a tool clinicians are using to look up medication status.

So I changed the pattern. Instead of querying SQL live on every search, I built a Power Automate flow that polls the on-premises database on a schedule, every 15 minutes, and syncs whatever's changed into Dataverse. The portal never talks to SQL directly and never waits on the gateway during a search. It reads straight from Dataverse, which is already sitting there with current data because the sync flow has been keeping it up to date in the background the whole time.

The search experience changed shape because of this. A healthcare worker still submits a search and a working record still gets created in Dataverse, but that record isn't waiting on a live SQL call anymore. It's matching against data that's already synced and already local to Dataverse. The result comes back fast because there's no gateway round trip sitting in the critical path of the user's request. The gateway round trip still happens, it's just happening on its own schedule in the background instead of on the user's clock.

System Architecture for Power Pages and APIs

Sync path, running independently every 15 minutes:

On-Premises SQL Server (source of truth)
โ†’ On-Premises Data Gateway
โ†’ Power Automate (scheduled trigger)
โ†’ Stored procedure pulls new and updated records
โ†’ Dataverse (records created or updated)

Search path, what the user actually experiences:

Search Form (Power Pages)
โ†’ Dataverse (working record created on submit)
โ†’ Dataverse query against already-synced data
โ†’ Power Pages (read-only status view rendered)

Document retrieval path, which stayed live because it has to:

Dataverse Record (metadata lookup)
โ†’ Comments / Documents Table (file identifiers)
โ†’ REST API (patient + carrier + file ID)
โ†’ Temporary Download URL
โ†’ Dataverse (URL written back to record)
โ†’ Power Pages (download button activated)

The document path is still real time on purpose. Temporary download URLs expire, so there's no version of caching those ahead of time that makes sense. Pre-fetching a URL that might be dead by the time someone clicks the button would just trade one problem for a worse one. Patient and medication data is different. It doesn't need to be query-fresh to the second, it needs to be current within a reasonable window, and 15 minutes is well inside what this client needed for that to count as current.

How Each Piece Came Together

Power Pages

Power Pages handled the front end. I built the patient search page, a read-only status and ticket details view, and the navigation connecting the two. The search form, results display, and document download scaffolding are all in place. Page navigation and form validation work without custom code, which was a meaningful time save given the deadline pressure.

Where Power Pages earns its reputation as more than a form builder is in how it connects to Dataverse natively. The read-only results page pulls directly from the Dataverse record without any custom data binding layer. That's not magic, but it's genuinely useful when you're moving fast, and it mattered even more once search stopped depending on a live flow execution and started depending on data that was already sitting there. Where Power Pages shows its limits is in dynamic behavior outside its form model. Wiring a download button to a flow-generated URL, handling the loading state while the document flow runs, giving the user feedback while the backend is working, those pieces require Liquid, JavaScript, or Power FX. The platform gives you a head start. It doesn't carry you all the way. It's also not the first time Power Pages has thrown me a curveball on something that looked simple on paper, password reset emails getting stuck in a pending state was its own debugging session entirely.

Custom Power Pages Web Page

Dataverse as a Working Data Store

The most useful architectural decision in this build was treating Dataverse as an application-layer working table that gets kept current by a background sync, rather than something that gets populated live on every user action. The sync flow owns the relationship with SQL. The portal owns the relationship with Dataverse. Those two things never have to wait on each other.

This pattern keeps the UI layer fully decoupled from the backend data source, which matters both for performance and for compliance. If the SQL schema changes, the portal doesn't need to change, only the sync flow does. And because everything the UI renders comes from Dataverse, the audit trail for what data was accessed and when stays inside the Microsoft ecosystem where the client's compliance controls already live. It's the same principle I keep coming back to with CRM data generally, a system is only as trustworthy as how reliably its source of truth gets fed, and here that meant making sure Dataverse was never guessing, it was always working from a sync that ran on a predictable schedule.

The custom Dataverse table covers both sides of the transaction: search inputs from the user, and results synced in from SQL, medication status, ticket details, carrier, prescriber, pharmacy, approval dates, document download URL. Once the sync flow has done its job, that table is the source of truth for everything on screen, and the user's search is really just a query against it.

Power Automate

Power Automate is doing two different jobs in this build now instead of one, and splitting them apart is what actually fixed the performance problem.

The sync flow runs on a 15 minute schedule, connects to SQL through the On-Premises Data Gateway, executes a stored procedure to pull new and updated records, and writes them into Dataverse. This is the flow that owns the gateway relationship and absorbs whatever latency exists on the SQL side, completely outside of any user's session.

The search flow is much lighter now. It creates the working record on submit and queries Dataverse, which is already populated. No gateway call sits in that path anymore. Apply to Each still handles cases where a patient has multiple associated records, but it's operating against Dataverse data instead of a live SQL response, which is a meaningfully faster operation.

One thing worth knowing if you're new to SQL over the gateway, this applies to either pattern: you can't execute raw query strings through it. Native ad hoc SQL execution isn't supported. What you can do is call stored procedures with parameters, which is actually the right approach anyway. Parameterized stored procedures give you better performance, better security posture, and a cleaner separation between application logic and data access. It's a good constraint even if it catches people off guard the first time. The practical implication is that you need your database team involved early. That call to loop them in is worth making on day one, not week three.

The On-Premises Data Gateway

Gateway setup is where Power Platform projects quietly lose days if you're not careful. The gateway software needs to live on a machine with stable network access to the SQL instance. Authentication between the gateway and SQL has to match your environment, whether that's Windows auth, SQL auth, or a service account, and that decision needs to be made in coordination with whoever owns the network and the database server. Once everything is configured and the gateway is running, it essentially becomes invisible. Getting there is where the friction is.

Moving to the polling pattern changed how I think about the gateway's role in this build. It's no longer something the user's experience depends on directly, it's infrastructure sitting behind a scheduled job. That's a much more forgiving place for it to live. If the gateway has a slow moment or SQL is under load, the worst case is the next sync cycle picks up the delay, not a healthcare worker staring at a spinner.

Document Architecture

Documents in this system aren't stored on the core patient record. They're linked through a separate comments and documents table that holds file metadata: file name, file identifier, content type, created and updated timestamps. The relationship is patient record to comments table to file metadata. Before you can retrieve a document, you have to query that relationship to get the file identifier, then pass that identifier to the REST API along with patient and carrier context.

In Power Automate this means you're running a multi-step lookup before you ever touch the external API. It's the kind of thing that looks clean on a data model diagram and requires careful flow design when you're actually implementing it, especially when a single patient record might have several associated documents that each need their own retrieval call.

The REST API

The document retrieval API accepts four parameters: patient number, person code, carrier code, and file identifier. It returns a status message, file name, content type, and a temporary download URL. The URL is what the portal actually needs. The rest is logging and display metadata.

Power Automate's HTTP action handles the call cleanly. The work is in what happens around it: parsing the JSON response to pull out the URL, handling non-200 responses and auth failures gracefully, and thinking carefully about when to fetch the URL relative to when the user actually needs it. Temporary URLs with short expiration windows create a UX design constraint that's easy to overlook. You don't want to retrieve the download URL when the record loads. You want to retrieve it when the user clicks the button, so there's no risk of surfacing an expired link.

Where It's At Right Now

Most of this is done and working. Infrastructure's in place, Dataverse is wired up, and the sync flow has been tested against the gateway and is pulling data on schedule without issue. What's left is mostly the document side, finishing the API integration, swapping in properly parameterized stored procedures, getting the download button in Power Pages actually talking to the URL the API hands back, and locking down permissions before this goes to production. I'll probably tune the 15 minute interval once the client's had it in front of real users for a few weeks and I can see how stale they're actually willing to let the data get. None of what's left is a mystery at this point, it's just the remaining hours.

Low Code Lessons in Highly Regulated Orgs

Low-code doesn't mean low-complexity. I've said some version of this before, but this build made it concrete in a way that's worth putting into words. Power Platform handled an on-premises SQL connection, a relational document model, a REST API integration, and a regulated healthcare environment with an aggressive deadline. That's not a simple use case. The platform carried it.

What it didn't do is reduce the number of things you need to understand to build it. I still had to figure out, the hard way, that a live query-on-submit pattern wasn't going to hold up, and redesign around a sync model once I saw where the latency actually lived. I still had to think through gateway authentication. I still had to design for stored procedure parameterization. I still had to think about where temporary URLs live in the data model and when to fetch them relative to user action. The platform compressed the implementation time on all of those things. It didn't replace the thinking behind them, and it definitely didn't catch the performance problem for me. I had to find that one by testing it and watching it struggle.

The bigger takeaway for me was the tool selection question. I have a preferred stack. Most consultants do. And there's real value in having deep reps on a set of tools because you know where the edges are and you move faster inside them. But "what am I comfortable with" and "what does this engagement actually call for" are two different questions, and conflating them is how you end up with the right answer for the wrong client. This project called for Power Platform. I built it on Power Platform. That's the whole decision.

If you're in a similar spot, evaluating whether Power Platform can carry something like this, or deciding between your preferred orchestrator and whatever the client's environment is already built on, I'd focus less on which tool is objectively better and more on which tool fits the actual constraints: timeline, compliance, environment, and governance. The best build is the one that ships, holds up under scrutiny, and doesn't create a support burden the client can't manage. Choose wisely, dear comrades.