📅 Mirror your calendars between SMA and MF (and any other Google org)

📅 Mirror your calendars between SMA and MF (and any other Google org)

If you work across both SMA and Moral Fabric, your colleagues in one org can't see your busy time in the other — even if you've shared the calendar across. This page explains why, and gives you a small Apps Script that mirrors events between the two so everything just works.

Why not just use a scheduling tool?

We tried meetergo and similar EU-native tools (Zeeg, Doodle, < Cal.com > hosted). All of them solve external booking pages well, but none of them fix the internal problem: when an SMA colleague does "Find a time" on you in Google Calendar, Google only checks your primary SMA calendar. Your MF events stay invisible. Same in reverse.
The only fix is to make sure the events actually live in both calendars. Mirroring via Apps Script does that in ~30 lines, costs nothing, and means:
  • Compass Colleagues see you as "Busy" in free/busy lookups
  • Calendar Google's appointment booking pages on either side respect both calendars
  • Lock Event details stay private to you (colleagues just see "Busy")
  • Money with Wings No per-user scheduling tool subscription

Prerequisites (one-time setup)

You need admin on both orgs, or someone who does, to enable cross-org sharing.
1. Both admins: allow external full-detail sharing
In each org's Admin console:
Apps → Google Workspace → Calendar → Sharing settings → External sharing options for primary calendars
Set to: "Share all information, and outside users can change calendars" (or "...but outsiders cannot change calendars" if you want safer — full details still show).
Changes can take up to 24h to propagate.
2. Share your calendars across
From your SMA Calendar:
  • Settings → Settings for my calendars → [your SMA calendar] → Share with specific people or groups
  • Add your MF email, permission: "See all event details"
  • Click the "Add this calendar" link in the email that arrives at MF
Repeat the other way (MF calendar → SMA email).
You can now see both calendars in both accounts. But colleagues still can't — that's what the script fixes.

The mirror script

You'll create two Apps Script projects: one in each account. They each pull from the other calendar and mirror events into your primary, marked private so colleagues see "Busy" only.

Setup (do twice — once per account)

    Go to < script.google.com > (logged into the target account — start with SMA)
    New project → paste the code below
    Edit the CONFIG block at the top
    Save (Ctrl/Cmd+S)
    Function dropdown: select syncCalendars → Run → authorize when prompted
    Check execution log: should say "Synced N events"
    Triggers (clock icon, left sidebar) → Add Trigger → syncCalendars, time-driven, every 15 minutes
    Repeat in the other account, swapping source/target IDs and the emoji prefix

Script

// CONFIG — edit these four lines
const SOURCE_CALENDAR_ID = 'your-other-email@otherorg.com'; // the calendar to pull FROM
const TARGET_CALENDAR_ID = 'primary'; // your primary calendar in THIS account
const DAYS_AHEAD = 30; // how far into the future to mirror
const MIRROR_TAG = '🧭'; // 🧭 for SMA-sourced, 🧵 for MF-sourced (or pick your own)

function syncCalendars() {
const now = new Date();
const end = new Date(now.getTime() + DAYS_AHEAD * 24 * 60 * 60 * 1000);

const source = CalendarApp.getCalendarById(SOURCE_CALENDAR_ID);
const target = CalendarApp.getCalendarById(
TARGET_CALENDAR_ID === 'primary' ? Session.getActiveUser().getEmail() : TARGET_CALENDAR_ID
);

if (!source) throw new Error('Source calendar not found. Is it shared and added?');
if (!target) throw new Error('Target calendar not found.');

// 1. Delete previously mirrored events in the future window
const existingMirrors = target.getEvents(now, end)
.filter(e => e.getTag('mirror') === 'true');
existingMirrors.forEach(e => e.deleteEvent());

// 2. Copy current source events (skip ones already mirrored in source = loop protection)
const sourceEvents = source.getEvents(now, end)
.filter(e => e.getTag('mirror') !== 'true');

sourceEvents.forEach(e => {
if (e.getMyStatus && e.getMyStatus() === CalendarApp.GuestStatus.NO) return;

const title = `${MIRROR_TAG} ${e.getTitle()}`;
let mirror;
if (e.isAllDayEvent()) {
mirror = target.createAllDayEvent(title, e.getAllDayStartDate(), e.getAllDayEndDate(), {
description: e.getDescription(),
location: e.getLocation()
});
} else {
mirror = target.createEvent(title, e.getStartTime(), e.getEndTime(), {
description: e.getDescription(),
location: e.getLocation()
});
}
mirror.setTag('mirror', 'true');
mirror.setVisibility(CalendarApp.Visibility.PRIVATE); // colleagues see "Busy" only
mirror.removeAllReminders();
});

console.log(`Synced ${sourceEvents.length} events.`);
}

How to find your calendar ID

In the account that received the shared calendar:
    Calendar → left sidebar → hover the shared calendar → three dots → Settings and sharing
    Scroll to "Integrate calendar" → copy the Calendar ID
    Paste into SOURCE_CALENDAR_ID

Cleanup script (optional, paste into the same project)

If you ever want to undo all mirrors in this account:
function removeAllMirrors() {
const target = CalendarApp.getCalendarById(Session.getActiveUser().getEmail());
const start = new Date(2020, 0, 1);
const end = new Date(2030, 0, 1);
const mirrors = target.getEvents(start, end).filter(e => e.getTag('mirror') === 'true');
mirrors.forEach(e => e.deleteEvent());
console.log(`Deleted ${mirrors.length} mirror events.`);
}
Run it once → all script-created events disappear → your real events are untouched.

What colleagues see

  • Compass or Thread prefix tells you at a glance which org the event originated in
  • Colleagues only see "Busy" (visibility is set to Private per event)
  • "Find a time" and Appointment Schedule conflict-checking now work properly
  • Your own primary calendar view shows the full title with emoji prefix

Caveats

  • Only future 30 days are mirrored (configurable via DAYS_AHEAD)
  • Recurring events get expanded into individual instances — fine for most cases
  • Declined events are skipped
  • Edits in the source propagate within 15 minutes
  • Don't invite people to mirror events — they're regenerated every sync
  • If you have very dense calendars (>1000 events), watch Apps Script quotas; usually fine

Why this beats paid tools for this use case

Title
Mirror script
meetergo / Zeeg / <Cal.com>
Internal "Find a time" works
Check Mark Button
Cross Mark (only fixes external booking)
Cost
€0
€13–25/user/mo
Privacy (event details hidden from colleagues)
Check Mark Button
Check Mark Button
Booking pages respect both calendars
Check Mark Button (via Google's native tool)
Check Mark Button
Round-robin / routing forms
Cross Mark
Check Mark Button
Maintenance
Low — script runs itself
Low — but vendor lock-in
If you need round-robin or smart routing forms (lead qualification, multi-host distribution), a paid tool still makes sense. For most MF–SMA dual-hatters, the script covers it.


Questions or improvements? Ping Ruben.