Pure Delphi library for Microsoft Graph API integration. Provides OAuth2 authentication with PKCE and typed clients for Mail, Calendar, Contacts, and SharePoint. No external dependencies beyond the Delphi RTL.
- Features
- Requirements
- Installation
- Quick Start
- Project Structure
- API Reference
- Demo Application
- Azure AD App Registration
- Token Storage
- License
- About GDK Software
- OAuth2 + PKCE authentication flow for Microsoft identity platform
- Mail -search, read, draft, send, delete, move messages, list folders, attachments
- Calendar -list, create, update, delete events, check schedule availability
- Contacts -search, create, update, delete contacts
- SharePoint -browse sites, list/search drive items, get file content
- Zero dependencies -uses only
System.Net.HttpClient(Delphi RTL) - Pluggable logging -
TLogProccallback, no global logger - Shared HTTP client -all Graph clients can share a single
TGraphHttpClient - Shared mailbox support -access shared/delegated mailboxes via
MailboxAddressproperty - Typed responses -all API calls return strongly-typed records (
TMailMessage,TCalendarEvent, etc.) - Interface-based -all clients implement interfaces (
IMailClient,ICalendarClient, etc.) for dependency injection
- Delphi 11 Alexandria or later (RAD Studio 11.x+)
- Azure AD App Registration with appropriate API permissions
Add the Source/OAuth2 and Source/Graph directories to your project's search path:
Source\OAuth2;Source\Graph
Then add the units you need to your uses clause:
uses
MSGraph.OAuth2.Types,
MSGraph.OAuth2.PKCE,
MSGraph.OAuth2.Client,
MSGraph.OAuth2.TokenStore,
MSGraph.Graph.Http,
MSGraph.Graph.Mail.Interfaces,
MSGraph.Graph.Mail,
MSGraph.Graph.Calendar.Interfaces,
MSGraph.Graph.Calendar,
MSGraph.Graph.Contacts.Interfaces,
MSGraph.Graph.Contacts,
MSGraph.Graph.SharePoint.Interfaces,
MSGraph.Graph.SharePoint;var Config: TOAuth2Config;
Config.ClientId := 'your-client-id';
Config.ClientSecret := 'your-client-secret';
Config.TenantId := 'your-tenant-id';
Config.RedirectUri := 'http://localhost:8080/oauth/callback';
Config.Scopes := TArray<string>.Create(
'openid', 'offline_access',
'Mail.Read', 'Mail.ReadWrite', 'Mail.Send',
'Calendars.ReadWrite', 'Contacts.ReadWrite',
'Sites.Read.All', 'User.Read'
);var PKCESession := TOAuth2PKCE.Generate;
var OAuthClient := TOAuth2Client.Create(Config);
var AuthUrl := OAuthClient.GenerateAuthorizationUrl(PKCESession);
// Open AuthUrl in browser, handle callback to receive authorization codevar Tokens := OAuthClient.ExchangeCodeForToken(Code, PKCESession.CodeVerifier);var Mail: IMailClient := TMailClient.Create(Tokens.AccessToken);
var SearchResult := Mail.SearchMessages('*', '', 10, 0);
forvar Msg in SearchResult.Messages do
WriteLn(Msg.Subject, ' - ', Msg.From.Address);if Tokens.IsExpiringSoon(300) thenbeginvar NewTokens := OAuthClient.RefreshAccessToken(Tokens.RefreshToken);
if NewTokens.RefreshToken.IsEmpty then
NewTokens.RefreshToken := Tokens.RefreshToken;
end;All Graph clients accept an existing TGraphHttpClient, allowing you to share one connection across multiple services:
var Http := TGraphHttpClient.Create(Tokens.AccessToken);
var Mail: IMailClient := TMailClient.Create(Http);
var Calendar: ICalendarClient := TCalendarClient.Create(Http);Set MailboxAddress on TGraphHttpClient to target a shared or delegated mailbox. When empty (default), endpoints use /me. When set, endpoints use /users/{address}:
var Http := TGraphHttpClient.Create(Tokens.AccessToken);
Http.MailboxAddress := 'projects@company.com';
var Mail: IMailClient := TMailClient.Create(Http);
var Messages := Mail.SearchMessages('*', '', 10, 0);This requires the Mail.Read.Shared and/or Mail.Send.Shared delegated permissions in your Azure AD app registration.
CreateDraft has an overload that takes custom headers. They are written to the message as internetMessageHeaders and travel with it when the draft is sent:
var Draft := Mail.CreateDraft('Subject', 'Body', ['recipient@company.com'], [], [], False,
[TMailHeader.Create('x-example-id', '42')]);
Mail.SendDraft(Draft.Id);Microsoft Graph requires a custom header name to start with x-, and accepts at most five custom headers per message. The library validates the headers before sending anything and raises EInvalidMailHeaderException with a readable message when a rule is broken:
| Rule | Rejected example |
|---|---|
| At most five custom headers per message | six headers |
| The name must not be empty | '' |
The name must start with x- (case insensitive) | Example-Id |
The name may contain printable ASCII only, and no : | x-example-id:42, x-example-id |
| The value must not contain control characters or line separators | 42<CR><LF>x-injected: yes |
| The same name must not be supplied twice (case insensitive) | x-example-id and X-Example-Id |
Names are sent exactly as supplied — the library does not change their casing.
Graph accepts internetMessageHeaders only when a message is created, so UpdateDraft does not offer this parameter.
Two things to know about the receiving side. Exchange Online maps a custom header name to a named property on first use, and does not store the value on that very first message — only later messages carrying the same header name keep their value. A transport rule or the header firewall can also strip custom headers in transit.
Source/
OAuth2/
MSGraph.OAuth2.Types.pas -Config records, token response, PKCE session, exceptions
MSGraph.OAuth2.PKCE.pas -PKCE code_verifier + code_challenge generation
MSGraph.OAuth2.Client.pas -OAuth2 flow: auth URL, token exchange, refresh
MSGraph.OAuth2.TokenStore.pas -Thread-safe in-memory token + PKCE storage
Graph/
MSGraph.Graph.Http.pas -Graph API HTTP client with error handling
MSGraph.Graph.JsonHelper.pas -JSON parsing utilities (TGraphJson)
MSGraph.Graph.Mail.Types.pas -Mail record types (TMailMessage, TMailFolder, etc.)
MSGraph.Graph.Mail.Interfaces.pas -IMailClient interface
MSGraph.Graph.Mail.pas -TMailClient implementation
MSGraph.Graph.Calendar.Types.pas -Calendar record types (TCalendarEvent, TAttendee, etc.)
MSGraph.Graph.Calendar.Interfaces.pas -ICalendarClient interface
MSGraph.Graph.Calendar.pas -TCalendarClient implementation
MSGraph.Graph.Contacts.Types.pas -Contact record types (TContact, TPostalAddress, etc.)
MSGraph.Graph.Contacts.Interfaces.pas -IContactsClient interface
MSGraph.Graph.Contacts.pas -TContactsClient implementation
MSGraph.Graph.SharePoint.Types.pas -SharePoint record types (TSite, TDriveItem)
MSGraph.Graph.SharePoint.Interfaces.pas -ISharePointClient interface
MSGraph.Graph.SharePoint.pas -TSharePointClient implementation
Examples/
Microsoft365Demo.dpr -Console demo with interactive menu
Microsoft365Demo.App.pas -Demo application logic
Microsoft365Demo.CallbackServer.pas -Indy HTTP server for OAuth callback
All library exceptions inherit from EMSGraphException:
| Exception | Raised by |
|---|---|
EMSGraphException | Base exception for all Microsoft365-4D errors |
EOAuth2Exception | Token exchange failures, invalid responses |
EGraphApiException | Graph API HTTP errors, missing access token |
ETokenStoreException | Missing tokens, expired PKCE sessions |
EInvalidMailHeaderException | Invalid custom mail header supplied by the caller |
EDeltaLinkExpiredException | An expired delta link, so a full resynchronisation is needed |
| Method | Description |
|---|---|
SearchMessages(Query, FolderId, Top, Skip) | Search or list messages |
GetMessage(MessageId) | Get full message by ID |
GetMessageAttachments(MessageId) | List attachments |
GetAttachmentContent(MessageId, AttachmentId) | Get attachment content |
CreateDraft(Subject, Body, To, Cc, Bcc, IsHtml) | Create draft |
CreateDraft(Subject, Body, To, Cc, Bcc, IsHtml, Headers) | Create draft with custom internet message headers |
UpdateDraft(MessageId, Subject, Body, To, Cc, Bcc, IsHtml) | Update existing draft |
SendDraft(MessageId) | Send a draft message |
DeleteDraft(MessageId) | Delete a draft |
MoveMessage(MessageId, FolderId) | Move message to folder |
ListMailFolders(ParentFolderId) | List mail folders |
GetMailboxSignature | Get HTML signature |
| Method | Description |
|---|---|
ListEvents(Start, End, Top, Timezone) | List calendar events in range |
GetEvent(EventId) | Get event details |
CreateEvent(Subject, Start, End, Location, Body, Attendees, IsAllDay) | Create event |
UpdateEvent(EventId, Subject, Start, End, Location, Body, Attendees, IsAllDay) | Update event |
DeleteEvent(EventId) | Delete event |
GetScheduleAvailability(Schedules, Start, End, Timezone) | Check availability |
| Method | Description |
|---|---|
SearchContacts(Query, Top) | Search or list contacts |
GetContact(ContactId) | Get contact details |
CreateContact(GivenName, Surname, Email, Phone, Company, JobTitle) | Create contact |
UpdateContact(ContactId, GivenName, Surname, Email, Phone, Company, JobTitle) | Update contact |
DeleteContact(ContactId) | Delete contact |
| Method | Description |
|---|---|
ListSites(Query, Top) | Search SharePoint sites |
GetSite(SiteId) | Get site details |
ListDriveItems(SiteId, FolderId, Top) | List items in drive/folder |
SearchDriveItems(SiteId, Query, Top) | Search drive items |
GetDriveItemContent(SiteId, ItemId) | Get item details + download URL |
The Examples/ folder contains a console application demonstrating the full OAuth2 flow and all Graph API operations.
Microsoft365Demo.exe --client-id "your-id" --client-secret "your-secret" --tenant-id "your-tenant" --redirect-uri "http://localhost:8080/oauth/callback"All parameters can also be provided interactively when omitted. Available options:
| Parameter | Description | Default |
|---|---|---|
--client-id | Azure AD application ID | (prompted) |
--client-secret | Azure AD client secret | (prompted) |
--tenant-id | Azure AD tenant ID | (prompted) |
--redirect-uri | OAuth2 redirect URI | http://localhost:8080/oauth/callback |
--port | Local callback server port | 8080 |
The demo provides an interactive menu with the following options:
- Authenticate (opens browser for Microsoft login)
- List messages
- Read message by ID
- Send email (create draft + send)
- List mail folders
- List calendar events
- Create calendar event
- Search contacts
- List SharePoint sites
- Refresh token manually
- Go to Azure Portal > Microsoft Entra ID > App registrations > New registration
- Set Redirect URI to your callback URL (Web platform), e.g.
http://localhost:8080/oauth/callback - Create a Client secret under Certificates & secrets
- Add API permissions (Microsoft Graph, Delegated):
| Permission | Description |
|---|---|
openid | Sign-in |
profile | User profile |
offline_access | Refresh tokens |
Mail.Read | Read mail |
Mail.ReadWrite | Create/edit drafts |
Mail.Send | Send mail |
MailboxSettings.Read | Read mailbox signature |
Calendars.ReadWrite | Calendar access |
Contacts.ReadWrite | Contacts access |
Sites.Read.All | SharePoint read access |
User.Read | User info |
Mail.Read.Shared | Read shared/delegated mailboxes |
Mail.Send.Shared | Send from shared/delegated mailboxes |
- Click Grant admin consent if you have admin rights
The included TTokenStore stores tokens in-memory only and is intended for demo/development use. For production:
- Persist tokens to a database, file, or OS-level credential store
- Encrypt refresh tokens and access tokens before storage
- Implement your own storage by subclassing or replacing
TTokenStore
MIT License. See LICENSE for details.
This library is MIT licensed and free to use. For companies that depend on it commercially we offer support and maintenance agreements with guaranteed response times, and sponsored development of features you need. Contact us at gdksoftware.com/contact-us or open an issue to get in touch.
Microsoft365-4D is developed by GDK Software, a Delphi-focused software company building developer tools, MCP integrations, and enterprise applications.