For Mac developers selling outside the App Store

Everything between your Mac app and a paying customer.

Licence keys that survive a flight and a refund. Signed, notarised, auto-updating DMGs from one command. And 35 checks that stop the release which would have failed silently on a Mac you don't own.

One-time purchase · Unlimited apps · Kept current as Apple moves

Free tool, no signup: eighteen checks against a Mac app you already shipped and its update feed. It tells you what is quietly broken for the people who downloaded it, and fine for you.

The full run from certificate to notarised DMG, in order.
The log is the tool's own output, word for word.

The App Store does five jobs.
Out here, they're yours.

Sell from your own site and you keep 100% minus payment fees, own your customers, and escape the sandbox. In exchange, Apple hands you this list and no tooling.

The jobHandled aloneWith the kit
Menu bar app shellNo Xcode template exists; tutorials are outdated, LLMs suggest deprecated APIsWorking skeleton, macOS 13+
NotarizationXcode notarises the app. The DMG you actually ship is a separate submission, and every release repeats the GUI by handOne script, both submissions, signature flags checked before the round-trip
DMG packaginghdiutil incantations; the DMG itself must be notarized too, which most guides skipSigned, notarized, stapled
Auto-updatesSparkle setup, EdDSA keys, appcast hosting, three silent failure modesWired, with delta updates
License keysVerification API, offline handling, refund handling, keychain storagePolar flow, 14-day grace, provider in one file

What's in the box

  • The licence layerPolar key verification, storage in the Keychain, 14-day offline grace, and defined behaviour for refunds and revoked keys. Everywhere else on this page a mistake costs you an afternoon. Here it costs revenue, and you hear about it months later from the customer it locked out. The provider lives in one file of 72 lines of code; everything above it is provider-agnostic.
  • The Hawser appUnlocks your kit, reads your Developer ID out of the keychain, takes the notary key from a file dialog, and writes the config for you. No text editor required to get shipping.
  • Config.xcconfigThe one file you edit. Name, bundle id, team, versions, update feed, product id. Everything follows it.
  • ship.shBuild → sign → notarize → staple → DMG. Verifies its own signature flags before wasting a round-trip to Apple, and refuses to ship what would silently fail.
  • appcast.shGenerates your signed update feed. Rejects an unsigned one, a failure Sparkle's own tooling lets through without a word.
  • Five guidesWritten from measured runs, not documentation folklore. The troubleshooting page is real incidents with exact fixes, and the quickstart has a section for shipping an app you already have.
# you edit one file:
Config.xcconfig
ShipKit.xcodeproj
Sources/
  ExampleFeatureView  ← or point ship.sh at your own project
  SettingsView        launch-at-login, tabs
  UpdaterView         Sparkle wired
  License*            Polar + grace period
scripts/
  ship.sh             one command → DMG
  appcast.sh          signed update feed
docs/
  01-quickstart … 05-troubleshooting

Whether this fits, before you read any further.

Every constraint is here rather than in the small print. If one of the right-hand rows is you, close the tab now and keep your $99. We would rather lose the sale than the afternoon you spend finding out.

Built for

  • An app you have already written, or one you have not started. The pipeline is not tied to the skeleton: point ship.sh at your own Xcode project and it ships that one, without modifying it. That is how Hawser itself is shipped.
  • Swift and SwiftUI. The skeleton and every example are Swift.
  • macOS 13 Ventura or later, built universal for Apple Silicon and Intel.
  • Selling outside the App Store, from your own site: Developer ID signing, notarisation, DMG, Sparkle updates, licence keys.
  • An Apple Developer Program membership you already have or are about to buy. $99/year, paid to Apple, unavoidable either way.

Not built for

  • The Mac App Store. There is no sandboxed variant. This is the outside-the-store pipeline and only that.
  • Objective-C or iOS. No Objective-C skeleton, and nothing here targets iOS.
  • macOS 12 or earlier. The deployment target is 13.0 and the skeleton uses APIs that need it.
  • CI. ship.sh runs on your machine, deliberately. There are no GitHub Actions templates.

One more, because it is the question we get asked instead of being told: the kit ships a Polar licence client, and the provider is not welded in. LicenseClient.swift is 72 lines of code whose entire job is one HTTP call. The 14-day offline grace, the keychain storage, the refund and revocation handling and the licensed / grace / unlicensed state machine all sit behind it and never learn who answered. Pointing it at Stripe, Paddle, Lemon Squeezy or your own server is rewriting one function, not unpicking the kit.

We fell in the holes so you don't

Three of the ten. Any one of these costs a lost afternoon, or worse, ships broken silently.

The default that expires your certificate

Apple's certificate page pre-selects "Previous Sub-CA". Accept it and your Developer ID dies in February 2027 no matter what the expiry says. The guide walks you past it; ship.sh's error text warns about it too.

The update feed that signs nothing

Add Sparkle after your first release and its own tool generates an unsigned feed with no warning. Updates then fail silently on every user's machine. appcast.sh verifies the signature and refuses to let it through.

The error that names nothing

errSecInternalComponent is codesign's way of saying "keychain permissions". It just never mentions keychains, or permissions. You get the one-line fix in the error message itself, when it happens, not after two hours of searching.

Read all ten, with the exact fix for each →

Three of the 35 checks, by name.

A number is easy to write and easy to doubt. These three are real stopping points in ship.sh and appcast.sh: 24 in the first, 11 in the second. Every one of them halts the run and prints the fix rather than a status code, and the three below share a property that makes them worth $99 on their own. Nothing on your machine looks wrong when they fire.

The update key that does not matchSUPublicEDKey
vs. your keychain

Before signing your update feed, the script reads the Sparkle public key baked into the app inside each archive and compares it with the private key in your keychain. If they are not a pair, it stops and prints both.

Without it: the feed publishes, the app fetches it, and Sparkle refuses every update with no visible error. Not a failed update dialog. Nothing. Your users simply stay on the version they have, and you find out from the ones who eventually email to ask why the app never updates.

The archive built before Apple answeredstapler validate
on the inner app

Each update archive is unpacked and the app inside it is checked for a stapled notarisation ticket. The usual cause of a failure here is specific enough that the script names it: the archive was copied out of dist/ by hand, from the build made to send to Apple, before the ticket existed.

Without it: you ship an update that Gatekeeper refuses on any Mac that is offline or behind a captive portal. It works perfectly on yours, because yours has already seen the ticket.

The debug entitlement that survivedget-task-allow
after signing

After the app is signed, the script reads the entitlements back out of the signature and refuses to continue if get-task-allow is still there. It also verifies the Hardened Runtime flag and the secure timestamp the same way, by reading the signature rather than trusting the command that made it.

Without it: Apple rejects the notarisation, but only after you have uploaded, waited, and lost the round trip. The check costs milliseconds and happens before the upload.

Counted in the shipping scripts on 4 September 2026, not estimated. The remaining 32 are the same shape: read back what actually happened, refuse to continue on a mismatch, and say what to do about it.

Eighteen of them run against an app you have already shipped, as a free tool →

Building it once is the easy half.

Apple has changed the rules of this pipeline before, and macOS 27 is in beta right now. Every change arrives the same way: a release that worked last month stops working, and nothing tells you why.

Our scripts stay current because we ship our own apps through them. When Apple moves, it breaks our releases first. We find it, fix it, and you get the fix.

A pipeline you assembled yourself gets updated when a customer emails you to say your app won't open.

Fixes and improvements to v1 are free, delivered through your original download link. Every release so far is on the changelog.

$99, once, for every app you ever ship.

No subscription, no per-app fee, no account. The full breakdown is on the pricing page: what the price buys, what you need on your side, and what the kit deliberately does not do. Before you pay rather than after.

See what it costs · $99

Not ready? Verify the sample or read the FAQ first. There is no trial, and the FAQ says why.