Ejtma is a meeting room booking system used by companies in Saudi Arabia. Almost every client asks the same question: "Will bookings show in Outlook?" They do. The harder question is whether Outlook bookings should flow back into Ejtma. This post explains how we built both directions with Microsoft Graph, and why most clients choose one-way.

Two modes, one config switch

// config/outlook.php 'enabled' => env('OUTLOOK_CONNECT', false), 'connection_type' => 'application', // or 'delegated' 'import_enabled' => true, 
  • Delegated (one-way). One service account signs in with OAuth and grants Calendars.ReadWrite on its own calendar. Ejtma writes bookings to Outlook. It reads nothing back.
  • Application (two-way). The company's Microsoft 365 admin grants the app tenant-wide calendar permissions. Ejtma writes to each meeting room's mailbox calendar and imports events booked from Outlook.

Two-way needs read access to many calendars across the company, not one service account's calendar. Many IT and security teams refuse that scope. That is why one-way is our default, and two-way is available on request.

Ejtma to Outlook: events and queued jobs

Booking actions fire domain events. A listener turns them into queued jobs:

Booking event Job
Published create the Outlook event
Updated update it
Rejected or deleted delete it

Graph calls can fail for many reasons: throttling, an expired token, a network error. Each job retries with growing backoff:

public $tries = 5; public $backoff = [10, 30, 60, 120, 300]; 

The endpoint depends on the mode:

$endpoint = $connectionType === 'delegated' ? 'me/calendar/events' : 'users/'.$reservation->room->email.'/calendar/events'; 

In delegated mode, the meeting room is added as a resource attendee. The booker is a required attendee and guests are optional. Times are sent in the Asia/Riyadh zone.

Keep the Graph IDs with the booking

After creating the event, we store what Graph returned in the booking's JSON metadata column:

$reservation->update(['metadata' => [ ...($reservation->metadata ?? []), 'graph_event_id' => $result['id'], 'graph_uid' => $result['uid'], 'graph_change_key' => $result['changeKey'], 'graph_room_email' => $reservation->room->email, 'graph_connection_type' => $connectionType, ]]); 

Storing the mode with each event matters. If a client later switches from one mode to the other, older events still update and delete through the endpoint they were created with.

Outlook to Ejtma: polling every five minutes

The import runs as a scheduled command:

Schedule::command('app:import-outlook-events') ->everyFiveMinutes() ->withoutOverlapping(); 

For each meeting room that has a mailbox, the command reads its calendar events page by page, with a page limit. Two guards keep it safe:

  • No echo. If an event's ID already exists in a booking's metadata, Ejtma created it, so the import skips it. Without this check, every booking would come back as a duplicate.
  • Same rules as the web. New events go through the same booking action as a booking made in the browser, with the same validation. If one event fails, the error is reported and the import moves on to the next event.

Token refresh in delegated mode

The access token is cached for 50 minutes. An hourly job refreshes it with the refresh token and stores the new refresh token when Microsoft returns one. If refresh fails after its retries, the job logs a critical error.

What we tell clients

  • One-way covers most needs. Staff see their bookings in Outlook, and the room calendar stays correct in Ejtma.
  • Two-way is useful when staff already book rooms from Outlook and will not change that habit. It needs an admin to approve broader calendar access first.

ملخص بالعربية: يشرح هذا المقال كيف يربط نظام حجز قاعات الاجتماعات الحجوزات بتقويم Outlook عبر Microsoft Graph. تعمل المزامنة باتجاه واحد افتراضيًا. وتتاح المزامنة باتجاهين عند الطلب، بعد موافقة مسؤول تقنية المعلومات. ويوضح المقال سبب هذا الاختيار.

Written by the engineering team at Renewable Systems in Riyadh, the makers of Ejtma.