❌

Reading view

SpamSieve 3.3.2

SpamSieve 3.3.2 is an update of my Mac e-mail spam filter that improves remote training, can export more rule information, and improves uninstallation, help, and more. The most significant news is that Microsoft has changed plans again. They will no longer be preventing users from continuing to use Legacy Outlook and in fact will keep it available for download as part of the combined Outlook app. This gives more time to adapt to their change in plans for AppleScript. Exchange (EWS) support remains an issue. Microsoft is still planning to cut off Legacy Outlook users, and Apple has yet to announce when Mail will support Graph. So far, I’ve not heard that it’s enabled in the macOS 27.2 beta.

We’re participating in a seasonal festival of artisanal software called SummerFest. For a limited time, you can get a 25% discount on new purchases of SpamSieve, EagleFiler, and other top Mac apps from our indie developer friends.

Some interesting engineering issues were:

  • Updating NSWorkspace APIs introduced a problem because with the old launchApplication(at:options:), the .withErrorPresentation option is off by default, whereas with the newer openApplication(at:configuration:completionHandler:), the promptsUserIfNeeded option is on by default.

  • Previously, Core Data would strip leading BOMs from NSStrings when saving to a SQLite database. With macOS 27, this happens for Swift Strings, too.

  • I have lots of unit tests that check for memory leaks. These caught a change where addPersistentStore(type:configuration:at:options:) now seems to return an autoreleased store. Without adding a pool, it looked like there was a leak.

  • Also, the tests were finding lots of “leaks” because KVO was retaining NSTabView, which retains a controller. Previously, XCTestCase.waitForExpectations(timeout:) would spin the run loop, and everything would get cleaned up. With Xcode 27 and macOS 27, I need to manually spin the run loop before waiting for the expectations.

  • During a software update, macOS may decide to reload the Dock icon. The application package no longer exists because it’s been replaced with a new version. This wasn’t a problem before because SpamSieve had been drawing to an image in memory and setting that as the Dock icon. Now, with the Liquid Glass–imposed removal of custom Dock icons, macOS tries to load the icon from disk, and rather than handling errors gracefully it will crash. I fixed this by setting an image with a memory representation before starting a software update.

  • SpamSieve 3.3.1 introduced a crash affecting a small but significant number of users when doing a Core Data fetch of a large number of objects. The private [_PFManagedObjectReferenceQueue _processReferenceQueue:] method was seemingly releasing an object that had already been released. What’s odd is that, as far as I can tell, none of the related app code had changed since the previous version, yet suddenly crashes started happening all the way from macOS 12 through macOS 27. If I had to guess, this was triggered by upgrading from Xcode 26.2 to Xcode 26.6. Maybe Swift’s ARC optimizations are doing something subtly different?

    The crash was triggered by an autorelease pool that I had inserted to promptly clean up any temporary objects after a fetch. (The actual fetched objects were returned and used outside of the pool.) Interestingly, both Gemini and ChatGPT were convinced that this is a well-known exception to standard memory management rules. They said it’s never safe to use managed objects outside of a pool they were fetched in because they depend on internal Core Data objects that the pool will have released. I was not able to find any basis for this “belief,” but it was easy enough to test. As expected, removing the pools (during the beta cycle) just made the same crashes happen slightly later.

    I think there’s probably something wrong in Core Data’s internal bookkeeping, but this does not seem to be a popular crash (and I’ve never seen it on any of my Macs), so my code must be doing something uncommon to trigger it. My current best guess is that it was related to calling refreshAllObjects() after saving. (I bet there’s some fancy bookkeeping to make that efficient.) I was doing this throughout the app to break retain cycles so that unused objects could be deallocated. But it wasn’t necessary in this particular case because the entity being fetched has no relationships. So far, removing the refresh seems to have stopped the crashes, but I won’t know for sure until more people have been using this version for a while.

Previously:

  •  

The End of EWS in Mail and AppleScript in Outlook

Microsoft:

Exchange Web Services (EWS) is approaching end of service in Exchange Online. We first announced this change was coming in 2018, when we announced that Exchange Web Services (EWS) will no longer receive functionality updates. In 2023, we announced that EWS will be disabled in Exchange Online in October 2026.

Today we’re announcing we will use a phased, admin controllable disablement plan that starts in October 2026 and concludes with a complete shutdown of EWS in 2027.

Apple:

Microsoft announced the upcoming end of service for Exchange Web Services (EWS) in Exchange Online. Apple is actively working with Microsoft to update Mail, Calendar, Contacts, Notes, and Reminders to use the Graph API in a future update to macOS 27, including a declarative configuration for device management services.

I’ve been eagerly awaiting Apple’s migration to Microsoft’s newer Graph API because EWS support in Apple Mail has long been buggy. It seems to have gotten worse in recent months, presumably due to server side changes as Microsoft prepares to shut it all down. Unfortunately, Apple is not as far along in the transition as I’d expected.

Jonathan Harbourne (and, alas, Claude):

What made a Mac feel like a Mac at work was the software that came with it. Mail that you could actually scan. A Calendar that was a separate application, so it could live permanently on a second screen instead of hiding behind a tab. Quick Look. Smart mailboxes. Spotlight that found things. Share sheets that worked everywhere. The system behaving as one coherent thing rather than a collection of vendor silos.

[…]

iOS and iPadOS Mail no longer depend on EWS. macOS Mail still does. Same company, same deadline, same protocol, and only one of the two platforms got the work done in time. Microsoft ended up writing the guidance Apple should have written, telling administrators in February that Apple Mail for Mac users would have to switch to Outlook until the applications were updated. […] Apple had years. It had a public deadline. It had, by Microsoft’s own account, one of the most widely used EWS applications in existence. And it let the clock run out.

[…]

Somewhere in every one of these migrations is a line in a change log that reads “users to move to Outlook”, as though a mail client were a fungible commodity, like swapping a stapler. […] This is not the death of the Mac at work. […] It is the death of the Mac experience at work.

Outlook for Mac feels very different from Apple Mail, and users are being squeezed from the Microsoft side, too. Microsoft will be ending the transition to New Outlook in October, but the new version is still missing many features that were present in Legacy Outlook (as well as Apple Mail). Microsoft had long promised that it would implement AppleScript in New Outlook before dropping support for the old version, but last week it changed course:

AppleScript workflows vary widely across organizations and often depend on behaviors from the classic app that don’t exist in the New Outlook. Because these patterns can’t be standardized, full compatibility isn’t feasible. We have decided not to move forward with this feature. We apologize for the inconvenience.

Back to Harbourne:

Access only ratchets one way. Mail was allowed because it worked, quietly, for years, over a protocol nobody was auditing. It has now been formally removed under a named cybersecurity policy. Getting it back would require someone in a security function to write a positive case for re-admitting a broad-permission third-party client to the corporate mail estate, and then put their name on it. Nobody’s career improves by doing that. Saying no costs nothing and is never wrong in hindsight.

So if Apple ships Graph support in Mail next year, the people who lost it will not get it back. The door didn’t swing shut. It was bolted, and the reason it was bolted is now written down in a policy document that will outlive the protocol that prompted it.

Apple has no leverage to change that, either. The organisations deploying Macs by the tens of thousands are not Apple partners in any meaningful sense. They are customers who buy boxes. There is no relationship to call on, no joint roadmap, no account team, nobody at Apple whose job it is to notice that a hundred thousand people just lost Mail.

October is when administrators will have to opt back into allowing EWS for a few more months, but some (like Harbourne’s) will be switching off support sooner.

Apple Mail and Outlook aren’t the only options, though. MailMaven supports both Graph and AppleScript.

Previously:

Update (2026-08-21): See also: Reddit and Mac Power Users.

Update (2026-08-24): See also: Mac Power Users.

Previously:

  •  

SpamSieve 3.3.1

SpamSieve 3.3.1 is an update of my Mac e-mail spam filter that improves the filtering accuracy and includes various optimizations, fixes, and updates for macOS 27. Some interesting engineering issues were:

  • I’m still supporting macOS 10.13, but the app can now build with the macOS 12 deployment target in preparation for Xcode 27. The way Swift handles calling deprecated APIs remains annoying. You need workarounds like protocols to suppress a warning in a particular region of code. Often, I am updating to a newer API, but I want to continue using the old one on earlier versions of macOS. Right now I am heavily testing on macOS 26, so I would like to only deploy the new code there. Sometimes the initial versions of APIs are buggy, and I don’t want to have to retest every feature on every version. But Swift doesn’t like me to gate the calls to macOS 26 and later if the new API was actually introduced in, say, macOS 11.

  • The most risky update was probably switching to the Swift API for Scanner. This is tricky because the current scan position changes from being based on UTF-16 indexes to String.Index. And in some cases my code was intentionally scanning into the middle of a combining character. I was really glad to have extensive tests here.

  • Recent versions of Tahoe introduced a Core Data bug that broke save recovery (when invalid Unicode can’t be written to the database and we have to fix it up first). When the error comes in, SpamSieve looks under the NSDetailedErrorsKey key, but the value of this key is "NSDetailedErrorsKey" while the actual error info is under the key "NSDetailedErrors".

  • Database corruption combined with a Core Data bug and two Swift behaviors were causing crashes. The root problem, I think, is that in this situation Core Data neglects to set the error pointer for -[NSManagedObjectContext countForFetchRequest:error:]. Normally, Swift would detect this and throw a generic nilError. However, this method signals errors by returning NSNotFound instead of nil. It’s annotated as swift_error(nonnull_error), which I think means that Swift doesn’t even look at the return value, only the (unset) error. So my code was receiving NSNotFound on the non-error path, treating it as a real count, and doing some math with it, which (by default in Swift) leads to a crash if there’s overflow. Now my code checks for NSNotFound and uses Int.addingReportingOverflow().

  • I reimplemented Core Data batch fetching because of two problems with the fetchBatchSize option. First, it logs spurious errors when fetching from multiple stores at once. Second, it leads to high memory use. It will load objects in batches, but it never releases the earlier batches. You can manually refault objects that you no longer need, but it will still hold onto the full object shells, which can add up. My implementation only holds onto IDs, and it can fetch the IDs using a different context than the objects to push more work to background threads.

  • I’m now using String.makeContiguousUTF8() in various places to avoid slow iteration of bridged strings.

  • There was a regression where some menu code could crash on macOS 10.13. -[NSMenu setItemArray:] is only available on macOS 10.14 and later, whereas the getter is always available. With old Objective-C, the two methods could be annotated with separate availabilities. The Objective-C property syntax doesn’t allow this, so there’s no way for the compiler to flag calls where the setter is missing.

  • The AppleScript support works much better with large data sets. The key, I think, is to never return an array to Cocoa for a scripting property/element. First, if you give it a lazy array it will sometimes try to access all the elements even if it only needs a few. And, second, AppleScript will run out of memory if the array is too large. By using indexed accessors, you can maintain laziness and only convert one object to a descriptor at a time. By caching a batch-fetching collection, you can limit the number of fetches and respond quickly when AppleScript requests individual objects.

Previously:

  •  
❌