Sentloop
Docs

iMessage: what Apple does not publish

A whole-SDK search for a scheduling API, and what turned up. Apple’s own Send Later cannot do it either.

Short answer: no, and not because SentLoop is missing something. Apple's own Messages "Send Later" cannot do it either.

This document records what was checked, how, and what the evidence was. Every claim below is either a symbol read out of the installed SDK or a quote from Apple's current documentation. Where something could not be verified on this machine, it says so instead of guessing.


Verification environment

Toolchain Apple Swift 6.3.2, target arm64-apple-macosx26.0
Installed Command Line Tools only - /Library/Developer/CommandLineTools
SDKs present MacOSX26.sdk, MacOSX26.5.sdk, MacOSX15.sdk, MacOSX15.4.sdk
Xcode Not installed. xcodebuild errors: "requires Xcode, but active developer directory is a command line tools instance"
iOS SDK Absent. No iPhoneOS SDK, no Simulator
Physical iPhone Not available to this environment

Two consequences, stated up front because they bound everything below:

  1. The iOS companion app in apple/SentLoop/ has not been compiled. It is written against documented API, and it is marked as unbuilt wherever it is referenced. Nobody should treat it as verified-working code.
  2. Tests A, B and C in the brief were not run. They require an iPhone, a Mac that can be powered off independently, and Xcode. Their answers below come from Apple's documentation, which happens to answer them unambiguously, and they are labelled as documentary rather than experimental.

What was actually inspected

1. The App Intents assistant schemas, read from the SDK

$SDK/System/Library/Frameworks/AppIntents.framework/Versions/A/Modules/
  AppIntents.swiftmodule/arm64e-apple-macos.swiftinterface        (11,752 lines)
  AppIntents.swiftmodule/arm64e-apple-ios-macabi.swiftinterface   (iOS ABI)

Every schema domain declared in the installed framework:

assistant  books  camera  files  journal  mail  photos  presentation
reader     spreadsheet    system  visualIntelligence  whiteboard  wordProcessor

Note what is present and what is not: there is a mail domain (SendDraftIntent, CreateDraftIntent, ReplyMailIntent, ArchiveMailIntent…) and there is no messages domain. Searching both interfaces for the string messages returns 0 matches, in the Catalyst/iOS interface as well as the macOS one.

A whole-SDK sweep for any scheduling symbol:

grep -rlE "scheduleMessage|sendLater|SendLater|scheduledMessage|ScheduledMessage" \
  $SDK/System/Library/Frameworks
→ no matches

So on the SDK installed here, no such API exists to call.

2. The messages domain, read from Apple's live documentation

The domain does exist in current documentation - it is simply newer than the SDK on this machine, which still uses the older @AssistantIntent macro rather than the @AppIntent(schema:) spelling the docs now show.

Pulled from Apple's documentation JSON (the HTML is JavaScript-rendered and returns nothing useful to a fetch):

Messages - "Make your messaging app's actions available to Apple Intelligence and Siri by adopting schemas for common messaging actions."

Actions in the domain:

draftMessage   editSentMessage   sendMessage
setMessageReadStatus             unsendMessage

There is no scheduling action in the domain. No scheduleMessage, no sendLater, no cancelScheduledMessage.

3. scheduledDate - the finding that looks like a yes and is not

.messages.sendMessage does carry a date. Apple's own example:

@AppIntent(schema: .messages.sendMessage)
struct SendMessageIntent {
    var content: AttributedString?
    var destination: MessageDestination
    var subject: AttributedString?
    var attachments: [IntentFile]
    var audioMessage: IntentFile?
    var locations: [GeoToolbox.PlaceDescriptor]
    var links: [URL]
    var scheduledDate: Date?          // ← it exists

    func perform() async throws -> some ReturnsValue<[MessageEntity]> {
        // ← and this body is yours
    }
}

This is the single most misreadable fact in the whole area, and the brief was right to flag it.

scheduledDate is an inbound parameter to an intent you implement. Siri parses "send this tomorrow at nine", fills in scheduledDate, and calls your perform(). Nothing in Apple's messaging infrastructure has been told anything. If your app does not itself implement scheduling, the date is simply a Date? you were handed and ignored.

Apple's integration guide removes any remaining doubt about direction. The entire sample is UnicornChat, a fictional third-party app that messages fictional unicorns:

"when a person says 'Siri, send a message to Bubbles', Apple Intelligence needs to resolve that Bubbles is a contact in the UnicornChat app, that UnicornChat is a messaging app that supports a set of defined messaging actions…"

Adopting .messages.sendMessage makes SentLoop available to Siri. It does not make Messages available to SentLoop.

4. INSendMessageIntent, read from the SDK header

$SDK/System/Library/Frameworks/Intents.framework/.../INSendMessageIntent.h

Every property on the intent:

recipients   outgoingMessageType   content   speakableGroupName
conversationIdentifier             serviceName   sender   attachments

No date. No scheduling parameter of any kind. The brief noted Apple's documentation says this intent does not inherently require an unlocked device; that is true and also irrelevant, because there is nothing here to schedule.

Direction is settled by the handler protocol, quoting the header:

@protocol INSendMessageIntentHandling <NSObject>
@required
/*! @abstract Handling method - Execute the task represented by the
    INSendMessageIntent that's passed in
    @discussion Called to actually execute the intent.
                The app must return a response for this intent. */

"The app must return a response" - the system calls your app. This is the same shape as the App Intents schema, one generation older.

5. Apple's own Send Later - the control experiment, answered by documentation

Messages has had a native Send Later since iOS 18: + → Send Later, up to 14 days out. The decisive property of it is the one that answers this brief:

"Scheduled messages require your iPhone to be powered on with an active internet connection at the scheduled time. If your device is off or disconnected, the message won't send automatically when you reconnect."

Apple's own first-party scheduling is device-dependent. The schedule lives on the iPhone, not on Apple's servers. There is no server-side queue to hand a message to, which is why no public API exposes one - the thing the brief is asking for does not exist for Apple either.

That single fact makes the acceptance test unachievable by anyone, including Apple, and it means no private API would help. There is no back door to a capability that is not implemented anywhere.


The ten questions, answered

1. Can SentLoop schedule a native iMessage through a public API? No. The messages App Intent domain exposes your app to Siri. There is no public API that inserts a message into Apple's Messages scheduling.

2. Can the operation be performed while the iPhone is locked? An App Intent can be invoked while locked, and INSendMessageIntent does not inherently require an unlocked device. But the operation in question does not exist, so the property has nothing to attach to. For SentLoop's own device- assisted path, a Shortcuts automation does run on a locked phone.

3. Can SentLoop initiate it while running? It can run its own intent while running. It cannot cause Apple Messages to schedule anything, running or otherwise.

4. Does the resulting scheduled message persist if SentLoop is terminated? There is no Apple-held schedule to persist. A schedule SentLoop keeps lives on SentLoop's server and survives the app being killed - but delivery still needs a device at the appointed moment.

5. Can the iPhone subsequently be offline? No. Apple's own Send Later fails this, per the quote above.

6. Can the Mac be powered off? Yes, irrelevantly - the Mac is not required by any iPhone-based path. It matters only for SentLoop's macOS agent route, which does require it awake.

7. Does Apple perform the eventual delivery? Apple's servers relay an iMessage once a device submits it. Apple does not hold a pending scheduled message on behalf of a third-party app, and does not hold one for its own Send Later either.

8. What exact public APIs are responsible?

  • AppIntents - @AppIntent(schema: .messages.sendMessage) and the rest of the messages domain, for exposing SentLoop to Siri and Apple Intelligence.
  • Intents - INSendMessageIntent / INSendMessageIntentHandling, the older equivalent, kept for compatibility and for share-sheet donation.
  • Shortcuts - a time-of-day Personal Automation can run a shortcut that sends, which is the only unattended-ish path and still needs the phone awake.

9. What limitations remain? Delivery requires a powered-on, online Apple device belonging to the user at the scheduled moment. Everything else is arrangeable.

10. What functionality is impossible without private APIs? Server-side, device-independent iMessage delivery is impossible with private APIs too. Private APIs could impersonate a Messages client on a device you control; they cannot create server-held scheduling that Apple has not built. Services advertising an "iMessage API" run racks of real Apple hardware - that is someone else's device staying awake, not an API.


Final acceptance test

User schedules an iMessage at 11:00 PM for 8:00 AM. At 11:01 PM SentLoop is closed, iPhone locked, Mac shut down. At 8:00 AM the recipient receives it.

TRUE ZERO-DEVICE AVAILABILITY:   NOT SUPPORTED
PUBLIC APPLE API:                NOT AVAILABLE
PRIVATE API REQUIRED:            NO - and it would not help
FALLBACK:                        Device-assisted scheduling (below)

Locked and app-closed are both fine. Powered off is not. The test as written fails on one clause, and it fails identically if you replace SentLoop with Apple's own Messages app.


What SentLoop builds instead

Device-assisted scheduling, labelled as exactly that in the product. The schedule is authoritative on the server; a device the user already owns performs the send at the appointed time.

SentLoop server   (holds the schedule, survives everything)
      ↓  device asks: is anything due?
iPhone Shortcut  or  macOS agent
      ↓  Apple's own Send Message action
Apple Messages infrastructure
      ↓
Recipient

Two device paths, both first-party surfaces, neither scripting the UI:

  • iPhone, agent/iphone.md - a Shortcuts Personal Automation. Runs on a locked phone with SentLoop closed. Needs the phone on.
  • Mac, agent/README.md - shortcuts run, a documented CLI. Needs the Mac awake.

And the honest part, which is already enforced in code rather than promised in prose: when no device is available, SentLoop does not sit on the message. The resolver moves it to a channel that sends from the server - email, Telegram, Discord, Slack, Mastodon, Bluesky - so the message still arrives. That behaviour is asserted by npm run verify:devices-off, which forces the no-Mac condition and proves the iMessage leg is skipped rather than attempted.

What SentLoop will therefore display, and will not exceed:

Scheduled through Apple-supported device automation.
Your device may need to remain available.

The sentence "Your iPhone and Mac do not need to remain available" is never shown for iMessage, because no test supports it and none can.


What is not proven here

Being explicit, because the brief asks for the boundary rather than a demo:

  • The iOS companion app is uncompiled. No Xcode on this machine.
  • Tests A, B and C were not executed. No iPhone, no independently power-cyclable Mac.
  • The messages schema is not in the SDK installed here; it was read from Apple's current documentation. On a machine with current Xcode the macro spelling should be confirmed before shipping.
  • Whether Apple's native Send Later is exposed as a Shortcuts action was not verified on-device. It would not change the conclusion, since Send Later is device-dependent regardless.

Anyone with an iPhone and Xcode can close these gaps, and the capability screen in the companion app is built to record the answers rather than assume them.