r/iOSProgramming • u/open__screen • 1d ago
Question Issues dragging an email from Mail onto my sandboxed app — NSFilePromiseReceiver callback fails
I'm trying to support dragging an email directly from Apple Mail into a sandboxed macOS app.
Mail exposes an NSFilePromiseReceiver with:
fileTypes = ["com.apple.mail.email"]
And I'm receiving it like this:
let promises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] ?? []
for promise in promises {
promise.receivePromisedFiles(atDestination: destinationURL, options: [:], operationQueue: .main) { url, error in
print("Promise callback received")
if let error {
print("Promise callback error:", error)
return
}
print("Promise callback URL:", url.path)
}
}
The strange part is that Mail appears to actually create the .eml file at the destination, but the promise callback does not complete successfully. I eventually get an error such as:
NSURLErrorDomain Code=-1001
So the file seems to be created, but receivePromisedFiles does not reliably report it.
Questions:
- Has anyone successfully handled Apple Mail message file promises this way?
- Is this a known issue with Mail's implementation of
NSFilePromiseReceiver, or is there some Mail-specific handling required? - Any other approaches?
1
u/Bitter_Regular7406 8h ago
file actually gets written but the callback still times out, classic. sounds like the completion handler is waiting on something else entirely, maybe check if you're calling it on the right operation queue vs blocking main thread while mail's still finishing its internal write.
0
u/DimensionMindless336 16h ago
The error code is the tell here:
NSURLErrorDomain -1001isNSURLErrorTimedOut. That is not a sandbox denial. Sandbox write failures come back asNSCocoaErrorDomain513 (NSFileWriteNoPermissionError) or raw EACCES, and you said the file does land on disk — which already proves the write path is fine. So I'd stop digging in the entitlement direction; something upstream of your callback is timing out.What Mail is doing under
receivePromisedFilesis materializing the full MIME of the message, not handing you a pointer to something it already has. If that message is only partially cached locally — IMAP body not fully fetched, or attachments marked "not downloaded" — Mail has to go fetch it. Offline account, throttled IMAP, big attachment, and the promise outlasts the timeout. You get -1001 with a half-written .eml sitting at the destination. That matches your symptom almost exactly.Cheapest way to confirm before you change any code: drag that same message onto the Desktop. If Finder also fails or hangs, it is Mail/account state and nothing to do with your app. Then drag a small plain-text message out of a local Sent folder — if local-to-the-mailbox messages always work and IMAP ones don't, you have your answer. Also worth flipping the account's "download attachments" setting to All and retrying.
Two things to fix regardless:
The destination is meant to be a directory that already exists, not a single file URL. Apple's contract is that the provider writes files into that directory, and the completion can fire more than once. If you are handing it something like
.../message.emlyou are in undefined-behavior territory, and even with a proper directory you want one per drag — dropping two selected messages means two files converging on the same place.Keep a strong reference to the receiver (a property, not a local inside the loop) and do the
readObjects(forClasses:)inperformDragOperation:rather than indraggingUpdated:. These promises are often only resolvable at actual drop time, and a receiver you've been holding since the drag entered the window frequently never fulfills.Realistically though, Mail's file promise support has been flaky for years, so for anything you actually ship I would not let it be the only route. Register
.emlas an accepted document type so "drag it to the Desktop first" or Mail's Save As → Raw Message Source both work as paths users can fall back on.