How I Built a Privacy-Safe Bridge Between Outlook and Apple Calendar

Why I Built This

Like many people working in enterprise environments, my work calendar lives inside Microsoft 365, while my personal life runs on Apple devices. Over time, that split became increasingly inconvenient. I wanted to see my work schedule on my personal devices so I could avoid conflicts with family plans, travel, and personal appointments. At the same time, I did not want to sign into my corporate Microsoft account on those personal devices, and I wanted to expose as little work data as possible.

What I ended up building was a small but surprisingly effective system: a read-only, auto-updating calendar bridge from Outlook to Apple Calendar.

It uses Power Automate to read my work calendar, generate an ICS calendar file, publish that file to Dropbox, and then expose it through a direct link that Apple Calendar can subscribe to.

The end result is simple to use, requires no local machine to stay on, and gives me a clean way to see my work schedule on my personal Apple devices and on my wife’s iPhone.


What I Wanted This Setup to Achieve

I had a few clear goals:

  1. I wanted to see my work schedule on my personal Apple devices.
  2. I did not want to sign into my corporate Microsoft account on those devices.
  3. I wanted the data exposure to be minimal and deliberate.
  4. I wanted the process to update automatically.
  5. I wanted to share that view with my wife so we could plan around work travel and important dates.

I also wanted this to run purely in the cloud, without depending on my laptop, desktop, or any other local workstation.


The Final Design

The final setup looks like this:

1. Outlook calendar in Microsoft 365
2. Power Automate scheduled flow
3. ICS file generated automatically
4. Dropbox file updated automatically
5. Apple Calendar subscribed to direct Dropbox ICS link

This works because Apple Calendar understands standard ICS feeds, Power Automate can build an ICS file, and Dropbox can serve a direct file link in a format Apple Calendar accepts.


Why I Did Not Use a Direct Microsoft-to-Apple Sync

My original thought was to find some way to expose a minimal view of my Outlook calendar directly, but there were a few problems with that approach.

First, I did not want my Apple devices to have access to my enterprise account. Second, built-in OneDrive and SharePoint links turned out not to be reliable as Apple Calendar subscription endpoints. Their links tend to go through browser views, redirects, or authentication layers, which Apple Calendar does not handle well for subscribed ICS calendars.

That is why I shifted from “calendar sync” to “read-only calendar publishing”.

This approach gave me far better control over privacy and behaviour.


Step 1: Create a Scheduled Cloud Flow in Power Automate

The first step is to create a scheduled cloud flow in Power Automate.

  • This flow acts like a cloud-hosted cron job. It runs on Microsoft’s servers, not on your own machine, so you do not need to keep a computer switched on.
  • When creating the flow, I used a recurrence trigger and set it to run every four hours. You could also choose every six hours if you want slightly fewer refreshes.
  • go to https://make.powerautomate.com

pic pic pic

The recurrence values looked like this:

1. Frequency: Hour
2. Interval: 4

pic

This means the flow wakes up automatically every four hours and rebuilds the ICS calendar file.


Step 2: Create a Rolling Time Window

The next step is to tell Power Automate what range of calendar events to export.

I chose to include events from the current moment up to 180 days in the future. This gave me enough visibility for work travel, conferences, and family planning, without making the exported file too large or cluttered.

To do this, I created two helper actions using Compose.

pic

The first helper action was called StartTimeISO and used this expression:

formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ssZ')

pic pic

The second helper action was called EndTimeISO and used this expression:

formatDateTime(addDays(utcNow(), 180), 'yyyy-MM-ddTHH:mm:ssZ')

This step mattered because the Outlook action I used did not accept raw expressions typed directly into its date fields. It needed real values passed in from other actions.

Your flow should now look like this:

pic


Step 3: Retrieve Outlook Calendar Events

Once the time window was defined, I added the Outlook action:

Get calendar view of events (V3)

pic

Next, select the calendar from your Outlook Account that you want to sync.

Set the Start Time and End Time, but clicking inside the text box, then selecting the Lightning button on the right to display previously set variables. Make sure you click “Outputs” for the appropriate StartTimeISO and EndTimeISO

pic pic

At this point, the flow was capable of retrieving all the calendar entries in that rolling 180-day window.

A small but important detail is that the V3 version of this Outlook connector returns the start and end values as strings. That affects how the event formatting needs to be done later.


Step 4: Initialise an ICS File in a String Variable

Once I had the Outlook events, I needed somewhere to build the calendar file itself.

I added an Initialize variable action with these settings:

Name: icsBody
Type: String

pic

pic

I populated it with the opening lines of a valid ICS file:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Work Calendar Export//EN
CALSCALE:GREGORIAN

pic

This variable became the working text buffer for the full calendar file.


Step 5: Loop Through Each Calendar Event

The next step was to process each event returned from Outlook.

I added an Apply to each action

  • Click inside the box and select “Data from previous step” and pointed it at the list of events returned from the Get calendar view of events (V3) step.

If the visual picker does not show the list clearly, the expression looks like this:

body('Get_calendar_view_of_events_(V3)')?['value']

pic pic pic

This tells Power Automate to iterate through the event collection one item at a time.


Step 6: Convert Each Meeting Into an ICS Event Block

Inside that loop, I added Append to string variable.

The variable name was icsBody, and the value I appended for each event looked like this:

This had an error

BEGIN:VEVENT
SUMMARY:@{items('Apply_to_each')?['subject']}
DTSTART:@{formatDateTime(items('Apply_to_each')?['start'], 'yyyyMMddTHHmmssZ')}
DTEND:@{formatDateTime(items('Apply_to_each')?['end'], 'yyyyMMddTHHmmssZ')}
END:VEVENT

Try this instead:

concat(
'BEGIN:VEVENT',
'\nSUMMARY:', items('Apply_to_each')?['subject'],
'\nDTSTART:', formatDateTime(items('Apply_to_each')?['start'], 'yyyyMMddTHHmmssZ'),
'\nDTEND:', formatDateTime(items('Apply_to_each')?['end'], 'yyyyMMddTHHmmssZ'),
'\nEND:VEVENT',
'\n'
)

pic pic pic

This was one of the most important parts of the setup.

It preserved:

  • The exact meeting title from Outlook
  • The start time
  • The end time

It did not include:

  • Attendees
  • Body text
  • Meeting notes
  • Teams links
  • Locations

That gave me a useful but narrow view of my schedule.

A subtle but important point here is that the V3 connector returns start and end directly as strings. I originally tried to reference start.dateTime, which failed. The correct form in this case was just start and end.


Step 7: Close the ICS File

After the loop finished, I added one more Append to string variable action outside the loop.

That action appended:

END:VCALENDAR

pic

At that point, the icsBody variable contained a valid, fully assembled ICS calendar file.


Step 8: Save the ICS File to Dropbox

The next question was where to store the file so Apple Calendar could subscribe to it reliably.

I first tried OneDrive, both business and personal, and later considered iCloud. Those approaches either failed with connection errors or did not provide a clean direct file endpoint.

Dropbox turned out to be the right answer.

I added a Dropbox action. Be sure to select the correct option that corresponds to Dropbox (see screen pic below). During the process might ask you for authentication credentials after which it will maintain access persistence with an OAuth token. Very cool.

Create file

I set the file name to:

jonn-atp-calendar.ics

and used icsBody as the file content.

pic pic

pic

Initially, I expected Dropbox might refuse to overwrite an existing file, so I explored delete-and-recreate logic. However, when I tested the flow, I found that the connector in my setup created the file successfully on the first run and then updated it successfully on the second run without any extra overwrite handling.

That simplified the final design considerably.


Step 9: Verify That the Flow Updates the Dropbox File

After adding the Dropbox action, I saved and ran the flow manually. The first run created the file successfully. I then ran it a second time to see whether it would update or fail. It updated successfully, which confirmed that the Dropbox step was good enough as-is. That made the final flow much cleaner.

At this point, the working flow was essentially:

UPDATE: It looks like the overwrite function is inconsistent so I replaced the “Create File - Dropbox” action with an “Update File - Dropbox” action.

Recurrence
StartTimeISO
EndTimeISO
Get calendar view of events (V3)
Initialize variable icsBody
Apply to each event
Append VEVENT block to icsBody
Append END:VCALENDAR
Create file in Dropbox

pic

UPDATED FLOW

pic


Once the file existed in Dropbox, the next goal was to make it available to Apple Calendar. Dropbox can create a share link for the file, but the normal share link is not the same thing as a direct calendar feed.

A typical Dropbox share link looks something like this:

[https://www.dropbox.com/scl/fi/...](https://www.dropbox.com/scl/fi/...)

If you paste that into Apple Calendar directly, it usually fails because Apple Calendar wants a direct file endpoint, not a browser-oriented sharing page.

To convert to a downloadable link:

1. Go to Dropbox
2. Right‑click file → Copy link
3. Convert: ...?dl=0 → ?dl=1

https://www.dropbox.com/s/xxxxx/blah-blah-blah.ics?dl=1


Step 11: Subscribe in Apple Calendar

On iPhone, I added the subscribed calendar through:

Settings
Calendar
Accounts
Add Account
Other
Add Subscribed Calendar

On Mac, the equivalent path is:

Calendar
File
New Calendar Subscription

In both cases, I pasted the Dropbox URL that we edited to make it downloadable and ICS-friendly.

As soon as I used the direct dropboxusercontent.com link, the subscription worked.

I later added the same calendar to my wife’s iPhone, and that worked too.


Deciding What Information to Expose

One of the most important design decisions was how much to reveal in the exported calendar.

I ultimately chose to include:

  • Meeting title
  • Start time
  • End time

I deliberately excluded:

  • Attendees
  • Notes
  • Descriptions
  • Meeting links
  • Other rich metadata

This struck the right balance for me. It gave enough detail to coordinate life, travel, and planning, while still keeping the published feed relatively minimal. If you wanted stricter privacy, you could replace the meeting titles with a generic label such as Busy - Work. In my case, I decided that exact titles were acceptable.


Solving the All-Day Event Problem

One of the most surprising issues I ran into had nothing to do with Power Automate. It was how Apple Calendar renders all-day events. In Outlook, an all-day entry often appears as a compact banner. In Apple Calendar, the same event takes up much more visual space and can become distracting, especially when there are several multi-day informational entries.

I originally used all-day events to mark things like:

  • Conferences
  • Travel dates
  • Important work milestones

But when those appeared in Apple Calendar, they were too visually heavy.

To solve that issue, I changed how I create informational entries in Outlook. Instead of making them all-day events, I now enter them as timed events from:

12:00 to 13:00

I also made the titles very descriptive, for example:

Computex Taipei June 1 to 6
Work Taipei June 15-18

For multi-day events, I repeat that noon event daily. This worked well for two reasons:

  1. It keeps Apple Calendar visually tidy
  2. It avoids time-zone confusion when moving between Vancouver and Taipei

I chose noon specifically because it is a safe anchor time. Since I travel between North America and Asia frequently, it is much less likely to shift to the wrong date across time zones than an early morning or late evening event.


Confirm That the Flow Runs Entirely in the Cloud

Another important question I wanted to answer was whether this setup depended on any of my own machines.

It does not.

Power Automate runs the scheduled flow on Microsoft’s cloud infrastructure. That means:

  • My laptop does not need to be on
  • My desktop does not need to be logged in
  • No local script or cron job is required

This was a major benefit because it made the whole solution feel truly hands-off.


Confirm That the Dropbox Connection Stays Logged In

I also wanted to understand whether the Dropbox connection would remain valid over time. When you connect Dropbox to Power Automate, it stores an authenticated connection using an OAuth token. In normal use, that connection persists and does not require you to sign in each time the flow runs.

The connection could break if you revoke it, change certain security settings, or if Dropbox invalidates the token. But under normal conditions, it should remain stable. If that connection ever fails, the flow run history would show the error and the connection can be re-established in Power Automate.


Lessons Learned

  • The biggest challenge was finding a hosting solution that Apple Calendar would accept
  • Direct file URLs matter more than the generation of the ICS itself
  • Testing the flow twice revealed Dropbox overwrite behaviour
  • The problem was not Power Automate, but file hosting and link formatting
  • Time-zone handling matters in calendar design

Troubleshooting

Dropbox file not updating - Post Mortem UPDATE

  • Run the flow twice to confirm behaviour
  • In most cases, Dropbox will overwrite automatically
  • The flow failed recently, and could not overwrite the existing Dropbox file; as a quick fix, I replaced the “Create File - Dropbox” action with a “Update File - Dropbox” action; at some point I might consider a more elegant logic flow but I assume there will always be an existing file after running test flows

pic

Calendar shows SSL or connection error

  • Likely cause is using the wrong Dropbox link format
  • Fix by converting to a direct dropboxusercontent.com URL

Flow fails with invalid Start Time or End Time

  • Do not use expressions directly in the Outlook action
  • Use Compose steps instead

Event formatting errors

  • Use start and end fields directly
  • Do not use .dateTime

All-day events clutter Apple Calendar

  • Replace them with timed noon events

Final Thoughts

What started as a small convenience became a robust and elegant solution.

I now have:

  • A fully automated calendar pipeline
  • No need to log into work account on personal devices
  • A clean view of work schedule on Apple Calendar
  • A shared view with my wife for planning

Most importantly, it runs quietly in the background and just works.


Summary

I used Power Automate to generate a read-only ICS file from my Outlook calendar, hosted it on Dropbox using a direct file URL, and subscribed to it in Apple Calendar. This created a clean, private, and fully automated bridge between my work and personal ecosystems.