Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Ews Laravel Package

garethp/php-ews

A PHP library for Microsoft Exchange Web Services (EWS). Manage mail, calendars, contacts, tasks, and folders on Exchange Server and Office 365 through a clean, object-oriented wrapper over the EWS SOAP API, with support for Exchange 2007 through 2019 and Exchange Online.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and check prerequisites. composer require garethp/php-ews pulls the client plus its SOAP/NTLM dependencies. You need ext-soap, ext-curl, and ext-json. Verify with php -m | grep -E 'soap|curl' before writing any code — missing ext-soap produces confusing "class not found" errors deep in the request pipeline rather than at boot.

Look here first, in this order:

  1. src/MailAPI.php and src/CalendarAPI.php — the high-level facades. Roughly 80% of day-to-day work (send mail, list inbox, create events, manage folders, attachments) is already wrapped here; read these before writing raw SOAP.
  2. src/API/Type/ — the generated typed objects (MessageType, BodyType, EmailAddressType, FileAttachmentType, CalendarItemType…). This directory is the EWS schema in PHP; when you need an operation the facades don't cover, you build one of these.
  3. src/API/Enumeration/ — EWS enum constants (BodyType::BODY_TYPE_HTML, DistinguishedFolderIdNameType::INBOX, Importance, ItemQueryTraversalType).
  4. The examples/ folder in the repo and the client's static factory methods (getClient, getClientFromUsername, getClientFromAutodiscover).

First use case: read the inbox and send a message.

use garethp\ews\MailAPI;

$mail = MailAPI::withUsernameAndPassword('mail.example.com', 'svc.user', 'secret');

// Last 10 inbox items
foreach ($mail->getMailItems(['itemsPerPage' => 10]) as $item) {
    printf("[%s] %s\n", $item->getFrom()->getMailbox()->getEmailAddress(), $item->getSubject());
}

// Send
$mail->sendMail('Deploy finished', '<p>Build <b>482</b> is live.</p>', ['[email protected]']);

First thing to configure: the timezone. Pass it explicitly at client construction (['timezone' => 'Europe/London']). EWS stores and returns UTC; the library shifts values using this setting. If you skip it you get silently wrong calendar times that only surface around DST boundaries — the worst class of bug to debug in production.

Implementation Patterns

Facade first, typed objects second, raw SOAP last. Start every feature with MailAPI/CalendarAPI. When the facade lacks the operation, build the typed request from src/API/Type and hand it to the client method that mirrors the EWS operation name (findItem, getItem, createItem, syncFolderItems, getAttachment…). Only drop to hand-rolled \SoapClient calls if the library is genuinely missing an operation — it rarely is.

Constructing a message by hand (for anything sendMail() can't express — custom headers, IsDeliveryReceiptRequested, internet message headers):

use garethp\ews\API\Type\MessageType;
use garethp\ews\API\Type\BodyType;
use garethp\ews\API\Type\EmailAddressType;
use garethp\ews\API\Type\MailboxType;

$msg = new MessageType();
$msg->setSubject('Quarterly report');
$msg->setBody(new BodyType(BodyType::BODY_TYPE_HTML, '<p>See attached.</p>'));
$msg->setToRecipients([(new MailboxType())->setEmailAddress(new EmailAddressType('[email protected]'))]);
$msg->setIsDeliveryReceiptRequested(true);

Attachments are a two-step concept. For inline sends, build a FileAttachmentType, setName(), setContent(file_get_contents($path)) and pass it in the attachment array. For attachments over a few MB on existing items, prefer CreateAttachment then SendItem/UpdateItem to avoid holding the whole base64 body plus the message in memory.

Search with restrictions, not PHP loops. Never fetch a whole folder and filter in PHP. Build a RestrictionType (IsEqualToType, ContainsType, AndType) against an indexed field URI, and page results with IndexedPageItemViewType. For large mailboxes this is the difference between a 2-second job and a timed-out one.

Delta sync beats polling. For mailbox or calendar mirroring, use SyncFolderItems with a persisted SyncState token: first call returns everything plus a state, subsequent calls return only changes (creates/updates/deletes) plus a new state. Store the token per folder in your DB and run it on a schedule. This is dramatically cheaper than date-range queries and it's the pattern the library is really built for.

Impersonation for multi-tenant / shared mailboxes. Authenticate once as a service account, then $client->setImpersonation('[email protected]') before issuing requests. Cache the authenticated client per process and switch the impersonation header per request rather than re-authenticating — auth round-trips dominate latency.

Wrap it in your own adapter. Put the library behind an interface (MailGateway) with domain-shaped methods (fetchSince(), sendTransactional()). The library's public surface is broad and version-sensitive; a thin adapter keeps upgrades to one file and makes it trivially mockable in tests (swap the adapter, not the SOAP client).

Queue and batch the writes. Sends and creates should go through a job queue with exponential backoff. When you have many items, batch GetItem with an array of ItemIdType rather than N sequential calls — it uses one throttle budget slot instead of N.

Gotchas and Tips

Basic auth is gone on Office 365. Microsoft deactivated Basic authentication for EWS in Exchange Online. Any code passing username/password to *.onmicrosoft.com/O365 will get a 401 forever. You must register an Azure AD app, request the EWS.AccessAsUser.All (delegated) or full_access_as_app (application, plus Exchange.ManageAsApp) scope, and inject an Authorization: Bearer <token> header / set the access token on the client's HTTP layer depending on the version you're on. Check the release notes for your pinned version — OAuth wiring has changed across releases.

NTLM on Linux. On-prem Exchange needs NTLM; this depends on curl being compiled with NTLM support and on passing the right auth options. Symptoms of a half-configured NTLM setup are opaque HTTP 401 SOAP faults with no body — turn on curl verbose logging first, then debug the SOAP.

ItemId + ChangeKey are a pair. Always persist both. The ChangeKey changes after every update, so a stale one yields ErrorInvalidChangeKey. On that error, re-fetch the item and retry rather than failing the job.

Item ID format. EWS IDs arrive base64-ish and version-dependent; if you're storing them in a column, size it generously (255+) and never trim. IDs from a different Exchange version or mailbox won't resolve.

Throttling is real and quiet. Exchange applies per-user budgets (concurrency, message rate). When exceeded you get ErrorServerBusy with a BackOffMilliseconds value in the response. Parse it, sleep that long, retry. Blind retries make it worse and can get the service account throttled harder.

Timezone + DateTime. Always pass DateTime objects with an explicit timezone, and always set the client timezone. All-day events need the EWS IsAllDayEvent flag plus midnight boundaries in the user's timezone, not UTC — a common off-by-one-day bug.

Memory on large fetches. getMailItems() hydrates full message bodies. For listings, fetch IDs first, page them, then hydrate a bounded batch. Attachments come back as base64 inside the response, so a 25 MB attachment costs far more than 25 MB of PHP memory — stream to disk via a temp file when you can.

SOAP faults are exceptions with a ResponseCode. Catch the library's exception, inspect the EWS response code, and branch: ErrorItemNotFound (treat as deleted), ErrorInvalidChangeKey (re-fetch), ErrorServerBusy (backoff), ErrorAccessDenied (auth/permissions — don't retry). Logging the raw XML request/response is worth the effort; enable it behind an env flag early.

Namespace casing bites on upgrades. The namespace has appeared as both garethp\ews and Garethp\EWS across versions. Pin the version in composer.json and check use statements after upgrading; case-insensitive filesystems hide this until you deploy to Linux.

Autodiscover is convenient but slow and occasionally wrong. Use it once during onboarding to discover the EWS URL, then persist that URL and construct the client directly. Don't run Autodiscover per request.

HTML vs text bodies. BodyType::BODY_TYPE_HTML is what you want for rendered mail; EWS will not escape your content, so sanitise anything user-supplied before it lands in a body, and don't forget a plain-text alternative if you care about deliverability.

Debugging tip: when a response parses into an object with null fields, the SOAP call usually succeeded and the request was wrong (missing ItemShape, wrong traversal, unset folder ID). Dump the outgoing XML and compare it against a known-good EWS request in EWSEditor before blaming the library.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings