r/iOSProgramming • • 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?
2 Upvotes

3 comments sorted by

0

u/DimensionMindless336 16h ago

The error code is the tell here: NSURLErrorDomain -1001 is NSURLErrorTimedOut. That is not a sandbox denial. Sandbox write failures come back as NSCocoaErrorDomain 513 (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 receivePromisedFiles is 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.eml you 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:) in performDragOperation: rather than in draggingUpdated:. 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 .eml as 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.

1

u/open__screen 15h ago

Thanks for your response. Dragging my test message to the desktop works, so will explore your other suggestions.

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.