NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #3704 most downloaded on PyPI
Client for Microsoft Exchange Web Services (EWS)
Last release 11 months ago
10 Oct 2025
Release timing varies
gaps range from 2 weeks to 7 months
Nearly every release is documented
notes for 59 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
11 years old
101 releases · first in 2016
Dropped support for Python 3.9 which is EOL per October 2025.
Account.version is now lazy and merely creating an Account will not throw errors if the specified credentials have insufficient permissions to the acc
Account.version is now lazy and merely creating an Account will not throw errors if the specified credentials
have insufficient permissions to the account. This only happens if an attempt is made to access the mailbox.One column per quarter.
Dropped support for Python 3.8 which is EOL per October 7, 2024.
- Fix access to shared folders
Remove timezone warnings in GetUserAvailability
GetUserAvailabilityNoVerifyHTTPAdapter for newer requests versionsFix traversal of public folders in Account.public_folders_root
Account.public_folders_rootAdd O365InteractiveConfiguration helper class to set up MSAL auth for O365.
O365InteractiveConfiguration helper class to set up MSAL auth for O365.exchangelib[msal] installation flavor to match the above.Fix various issues related to public folders and archive folders
Contact.im_addressesFix ErrorAccessDenied: Not allowed to access Non IPM folder caused by recent changes in O365.
ErrorAccessDenied: Not allowed to access Non IPM folder caused by recent changes in O365.Allow setting a custom Configuration.max_conections in autodiscover mode
Configuration.max_conections in autodiscover modeFix QuerySet operations on shared folders
- Bugfix release
Fix bug where certain folders were being assigned the wrong Python class.
Fix PyPI package. No source code changes.
Deprecated RetryPolicy.may_retry_on_error. Instead, add custom retry logic in RetryPolicy.raise_response_errors.
Account(..., autodiscover=True) is supported again.RetryPolicy.may_retry_on_error. Instead, add custom retry logic
in RetryPolicy.raise_response_errors.exchangelib.util.RETRY_WAIT to BaseProtocol.RETRY_WAIT.Added support for SOAP-based autodiscovery, in addition to the existing POX (plain old XML) implementation. You can specify the autodiscover implement
autodiscover argument:
Account(..., autodiscover="soap") or Account(..., autodiscover="pox"). POX
is still the default.Added new OAuth2LegacyCredentials class to support username/password auth over OAuth.
OAuth2LegacyCredentials class to support username/password auth
over OAuth.Fixed token refresh bug with OAuth2 authentication, again
Fixed Protocol.get_free_busy_info() when called with +100 accounts.
Protocol.get_free_busy_info() when called with +100 accounts.Autodiscovery.DNS_RESOLVER_ATTRS["timeout""]) and the total query lifetime
(Autodiscovery.DNS_RESOLVER_LIFETIME) separately.- Bugfix release
- Bugfix release
Fixed field name to match API: BaseReplyItem.received_by_representing to BaseReplyItem.received_representing
BaseReplyItem.received_by_representing to
BaseReplyItem.received_representingreceived_by and received_representing to MeetingRequest,
MeetingMessage and MeetingCancellationAppointmentStateField.CANCELLED enum value.Fixed issue where creating an Account with autodiscover and no config would never set a default retry policy.
Removed deprecated methods EWSTimeZone.localize(), EWSTimeZone.normalize(), EWSTimeZone.timezone() and QuerySet.iterator().
ALL_OCCURRENCIES to ALL_OCCURRENCES in exchangelib.items.basePersona.orgnaization_main_phones to Persona.organization_main_phonesEWSTimeZone.localize(), EWSTimeZone.normalize(),
EWSTimeZone.timezone() and QuerySet.iterator().chunk_size and page_size in querysets and services. Add a
new QuerySet.chunk_size attribute and let it replace the task that
QuerySet.page_size previously had. Chunk size is the number of items we send
in e.g. a GetItem call, while page_size is the number of items we request
per page in services like FindItem that support paging.FolderCollection.subscribe_to_[pull|push|streaming]() now return a single
subscription instead of a 1-element generator.FolderCollection now has the same [pull|push|streaming]_subscription()
context managers as folders.Fix filtering on array-type extended properties.
GetStreamingEvents responses are now raised.Bug fixes for paging in multi-folder requests.
tzlocal>=4.1Support microsecond precision in EWSDateTime.ewsformat()
EWSDateTime.ewsformat()multiprocessing module to allow running in AWS Lambdatzlocal>=4Make FileAttachment.fp a proper BytesIO implementation
FileAttachment.fp a proper BytesIO implementationCalendarItem.recurrence_id fieldSingleFolderQuerySet.resolve() to aid accessing a folder shared by a different account:from exchangelib import Account
from exchangelib.folders import Calendar, SingleFolderQuerySet
from exchangelib.properties import DistinguishedFolderId, Mailbox
account = Account(primary_smtp_address="some_user@example.com", ...)
shared_calendar = SingleFolderQuerySet(
account=account,
folder=DistinguishedFolderId(
id=Calendar.DISTINGUISHED_FOLDER_ID,
mailbox=Mailbox(email_address="other_user@example.com"),
),
).resolve()
Support updating items in Account.upload(). Previously, only insert was supported.
Account.upload(). Previously, only insert was supported.Contact.manager_mailbox and Contact.direct_reports.text_body field on item attachments.Fixed bug when updating indexed fields on Contact items.
Contact items.CalendarPermission items in the permission_set field.Add Folder.move() to move folders to a different parent folder.
Folder.move() to move folders to a different parent folder.Add context managers Folder.pull_subscription(), Folder.push_subscription() and Folder.streaming_subscription() that handle unsubscriptions automatica
Folder.pull_subscription(), Folder.push_subscription() and
Folder.streaming_subscription() that handle unsubscriptions automatically.Move util._may_retry_on_error and and util._raise_response_errors to RetryPolicy.may_retry_on_error and RetryPolicy.raise_response_errors, respectivel
util._may_retry_on_error and and util._raise_response_errors to
RetryPolicy.may_retry_on_error and RetryPolicy.raise_response_errors, respectively. This allows for easier
customization of the retry logic.Add support for synchronization, subscriptions and notifications. Both pull, push and streaming notifications are supported. See https://ecederstrand.
This means that .iterator() is now a no-op and marked as deprecated. ATTENTION: If you previously relied on caching of results in QuerySet, you need t…
max_connections option for the Configuration class, to increase the session pool size on a per-server,
per-credentials basis. Useful when exchangelib is used with threads, where one may wish to increase the number of
concurrent connections to the server.Message.mark_as_junk() and complementary QuerySet.mark_as_junk() methods to mark or un-mark messages as junk
email, and optionally move them to the junk folder.Folder.get_user_configuration().Persona objects as returned by QuerySet.people() now support almost all documented fields.QuerySet.people() to call the GetPersona service if at least one field is requested that is not supported
by the FindPeople service.QuerySet. It's not necessary in most use cases for exchangelib, and the memory
overhead and complexity is not worth the extra effort. This means that .iterator()
is now a no-op and marked as deprecated. ATTENTION: If you previously relied on caching of results in QuerySet, you
need to do you own caching now.date, datetime and zoneinfo.ZoneInfo objects as values for fields and methods. This lowers the
barrier for using the library. We still use EWSDate, EWSDateTime and EWSTimeZone for all values returned from
the server, but these classes are subclasses of date, datetime and
zoneinfo.ZoneInfo objects and instances will behave just like instance of their parent class.Change Kerberos dependency from requests_kerberos to requests_gssapi
requests_kerberos to requests_gssapiEWSDateTime.from_datetime() accept datetime.datetime objects with tzinfo objects that are dateutil
, zoneinfo and pytz instances, in addition to EWSTimeZone.Allow overriding dns.resolver.Resolver class attributes via Autodiscovery.DNS_RESOLVER_ATTRS.
dns.resolver.Resolver class attributes via Autodiscovery.DNS_RESOLVER_ATTRS.This means that the ÈWSTimeZone methods timezone(), normalize() and localize() methods are now deprecated.
EWSTimeZone to be implemented on top of the new zoneinfo module in Python 3.9 instead of pytz
. backports.zoneinfo is used for earlier versions of Python. This means that the
ÈWSTimeZone methods timezone(), normalize() and localize() methods are now deprecated.EWSTimeZone.from_dateutil() to support converting dateutil.tz timezones to EWSTimeZone.CalendaItem.appointment_state, CalendaItem.conflicting_meetings and
CalendarItem.adjacent_meetings fields.Message.reminder_message_data field.Contact.manager_mailbox, Contact.direct_reports and Contact.complete_name fields.Item.response_objects field.Task.due_date and Tas.start_date fields from datetime to date fields, since the time was being truncated
anyway by the server.Task.recurrence field.Contact.user_smime_certificate and Contact.ms_exchange_certificate. This means that
all fields on all item types are now supported.Fix bug leading to an exception in CalendarItem.cancel().
CalendarItem.cancel()..order_by() in edge cases where sorting must be done client-side..filter(foo__in=[]) to return an empty result. This was previously undefined behavior. Now we
adopt the behaviour of Django in this case. This is still undefined behavior for list-type fields.pdoc3.Remove use of ThreadPool objects. Threads were used to implement async HTTP requests, but were creating massive memory leaks. Async requests should be
ThreadPool objects. Threads were used to implement async HTTP requests, but were creating massive
memory leaks. Async requests should be reimplemented using a real async HTTP request package, so this is just an
emergency fix. This also lowers the default
Protocol.SESSION_POOLSIZE to 1 because no internal code is running multi-threaded anymore.CalendarItem(is_all_day=True, ...)) now accept EWSDate
instances for the start and end values. Similarly, all-day calendar items fetched from the server now
return start and end values as EWSDate instances. In this case, start and end values are inclusive; a one-day
event starts and ends on the same EWSDate value.RecurringMasterItemId and OccurrenceItemId elements that allow to request the master recurrence
from a CalendarItem occurrence, and to request a specific occurrence from a CalendarItem master
recurrence. CalendarItem.master_recurrence() and
CalendarItem.occurrence(some_occurrence_index) methods were added to aid this traversal.
some_occurrence_index in the last method specifies which item in the list of occurrences to
target; CalendarItem.occurrence(3) gets the third occurrence in the recurrence.Contact.birthday and Contact.wedding_anniversary from EWSDateTime to EWSDate
fields. EWS still expects and sends datetime values but has started to reset the time part to 11:59. Dates are a
better match for these two fields anyway.len(some_queryset). It had the nasty side-effect of forcing
list(some_queryset) to run the query twice, once for pre-allocating the list via the result of len(some_queryset),
and then once more to fetch the results. All occurrences of
len(some_queryset) can be replaced with some_queryset.count(). Unfortunately, there is no way to keep
backwards-compatibility for this feature.Account.identity, an attribute to contain extra information for impersonation. Setting
Account.identity.upn or Account.identity.sid removes the need for an AD lookup on every request.
upn will often be the same as primary_smtp_address, but it is not guaranteed. If you have access to your
organization's AD servers, you can look up these values once and add them to your
Account object to improve performance of the following requests.The max_wait argument to FaultTolerance changed semantics. Previously, it triggered when the delay until the next attempt would exceed this value. It
max_wait argument to FaultTolerance changed semantics. Previously, it triggered when the delay until the next
attempt would exceed this value. It now triggers after the given timespan since the first request attempt.max_items (#710)Removed the legacy autodiscover implementation.
QuerySet.depth() to configure item traversal of querysets. Default is Shallow except for the CommonViews
folder where default is Associated.Account.protocol after getting an UnauthorizedError now works.The new Autodiscover implementation added in 2.2.0 is now default. To switch back to the old implementation, set the environment variable EXCHANGELIB_
EXCHANGELIB_AUTODISCOVER_VERSION=legacy.Added support for specifying a separate retry policy for the autodiscover service endpoint selection. Set via the exchangelib.autodiscover.legacy.INIT
exchangelib.autodiscover.legacy.INITIAL_RETRY_POLICY module variable for the the old autodiscover
implementation, and via the
exchangelib.autodiscover.Autodiscovery.INITIAL_RETRY_POLICY class variable for the new one.RootOfHierarchy.permission_set field. It was causing too many failures in the wild.Account.ad_response.EXCHANGELIB_AUTODISCOVER_VERSION=new. The old one is still the default
if the variable is not set, or set to EXCHANGELIB_AUTODISCOVER_VERSION=legacy.Item.mime_content field was switched back from a string type to a bytes type. It turns out trying to decode
the data was an error (see issue #709).- Bugfix release.
Added support for OAuth 2.0 authentication
RelativeMonthlyPattern and RelativeYearlyPattern where the weekdays field was thought to be a
list, but is in fact a single value. Renamed the field to weekday to reflect the change.Account.delegates.ConvertId service. Available as Protocol.convert_ids().Fixed a bug where version 2.x could not open autodiscover cache files generated by version 1.x packages.
Item.mime_content is now a text field instead of a binary field. Encoding and decoding is done automatically.
Item.mime_content is now a text field instead of a binary field. Encoding and decoding is done automatically.
The Item.item_id, Folder.folder_id and Occurrence.item_id fields that were renamed to just id in 1.12.0, have
now been removed.
The Persona.persona_id field was replaced with Persona.id and Persona.changekey, to align with the Item
and Folder classes.
In addition to bulk deleting via a QuerySet (qs.delete()), it is now possible to also bulk send, move and copy items
in a QuerySet (via qs.send(), qs.move() and qs.copy(), respectively).
SSPI support was added but dependencies are not installed by default since it only works in Win32 environments.
Install as pip install exchangelib[sspi] to get SSPI support. Install with pip install exchangelib[complete] to
get both Kerberos and SSPI auth.
The custom extern_id field is no longer registered by default. If you require this field, register it manually as
part of your setup code on the item types you need:
from exchangelib import CalendarItem, Message, Contact, Task
from exchangelib.extended_properties import ExternId
CalendarItem.register("extern_id", ExternId)
Message.register("extern_id", ExternId)
Contact.register("extern_id", ExternId)
Task.register("extern_id", ExternId)
The ServiceAccount class has been removed. If you want fault tolerance, set it in a
Configuration object:
from exchangelib import Configuration, Credentials, FaultTolerance
c = Credentials("foo", "bar")
config = Configuration(credentials=c, retry_policy=FaultTolerance())
It is now possible to use Kerberos and SSPI auth without providing a dummy
Credentials('', '') object.
The has_ssl argument of Configuration was removed. If you want to connect to a plain HTTP endpoint, pass the full
URL in the service_endpoint argument.
We no longer look in types.xsd for a hint of which API version the server is running. Instead, we query the service
directly, starting with the latest version first.
- Bugfix release.
Fix bug that left out parts of the folder hierarchy when traversing account.root.
account.root.Add support for reading and writing PermissionSet field on folders.
PermissionSet field on folders.Add Protocol.expand_dl() to get members of a distribution list.
Protocol.expand_dl() to get members of a distribution list.Lower the session pool size automatically in response to ErrorServerBusy and ErrorTooManyObjectsOpened errors from the server.
inbox.all()[9000] and inbox.all()[9000:9001])
is now efficient.Attachment.fp context manager.For all classes that have an ID, the ID can now be accessed using the id attribute. Backwards compatibility and deprecation warnings were added.
Item.item_id, Folder.folder_id and Occurrence.item_id to just
Item.id, Folder.id and Occurrence.id, respectively. This removes redundancy in the naming and provides
consistency. For all classes that have an ID, the ID can now be accessed using the id attribute. Backwards
compatibility and deprecation warnings were added.some_folder // 'sub_folder' // 'leaf'
(double-slash) syntax.pip install exchangelib[kerberos] to get Kerberos support.Improve back off handling when receiving ErrorServerBusy error messages from the server
ErrorServerBusy error messages from the serverAccount.root and its children would point to the root folder of the connecting account instead of
the target account when connecting to other accounts.Add experimental Kerberos support. This adds the pykerberos package, which needs the following system packages to be installed on Ubuntu/Debian system
pykerberos package, which needs the following system packages to be
installed on Ubuntu/Debian systems: apt-get install build-essential libssl-dev libffi-dev python-dev libkrb5-dev.- Bugfix release
- Bugfix release
Removed Folder.get_folder_by_name() which has been deprecated since version 1.10.2.
Added cancel to CalendarItem and CancelCalendarItem class to allow cancelling meetings that were set up
Added accept, decline and tentatively_accept to CalendarItem
as wrapper methods
Added accept, decline and tentatively_accept to
MeetingRequest to respond to incoming invitations
Added BaseMeetingItem (inheriting from Item) being used as base for MeetingCancellation, MeetingMessage,
MeetingRequest and MeetingResponse
Added AssociatedCalendarItemId (property),
AssociatedCalendarItemIdField and ReferenceItemIdField
Added PostReplyItem
Removed Folder.get_folder_by_name() which has been deprecated since version 1.10.2.
Added Item.copy(to_folder=some_folder) method which copies an item to the given folder and returns the ID of the new
item.
We now respect the back off value of an ErrorServerBusy
server error.
Added support for fetching free/busy availability information ofr a list of accounts.
Added Message.reply(), Message.reply_all(), and
Message.forward() methods.
The full search API now works on single folders and collections of folders, e.g. some_folder.glob('foo*').filter()
,
some_folder.children.filter() and some_folder.walk().filter().
Deprecated EWSService.CHUNKSIZE in favor of a per-request chunk_size available on Account.bulk_foo() methods.
Support searching the GAL and other contact folders using
some_contact_folder.people().
Deprecated the page_size argument for QuerySet.iterator() because it was inconsistent with other API methods. You
can still set the page size of a queryset like this:
qs = a.inbox.filter(...).iterator()
qs.page_size = 123
for item in items:
print(item)
Added support for registering extended properties on folders.
Your coding agent can read these notes before it upgrades. Set up the MCP server →