← Back to Research

iOS Application · Native · In Progress

Morning Brief for iOS

One calm view of the shape of the day — and it reads that view out loud at a time you set, with no Shortcut and no tapping. A native rewrite of the browser briefing system, built against the grain of what iOS will actually let an app do in the morning.

Morning Brief app icon: a single ink stroke over a clay-coloured sun
Platform
iOS 17+, iPhone only, portrait
Stack
SwiftUI, EventKit, AVFoundation
Dependencies
None — no third-party packages
Language
Korean-only bundle (한국어 전용)
Distribution
TestFlight, via Codemagic or GitHub Actions
Data
On-device; no server, no account

What the app reads, what stays on the device, and the one case in which anything leaves it are set out in the privacy policy. Common questions and where to write about a bug are on the support page (both 한국어 · English).

The premise

A brief you do not have to open

The browser version of Morning Brief answers a question — what is the shape of today? — but it still has to be visited. On a phone, the visit is the whole cost: the morning is the one time of day when picking up a screen is the thing you are trying to avoid. So the iOS app inverts it. The brief comes to you, spoken, at a time you set, and the screen is optional.

The interface keeps the printed structure: a day-date line, one serif headline, one unbroken terrain stroke with meeting dots and a single clay accent, then three time blocks. Below a hard edge sit the two lists — 확인이 필요한 일 above 이미 정리된 일. Everything down to the third time block is what gets read aloud. Everything below the edge is what the second notification points at.

The build is Korean-only, deliberately. The interface, every generated sentence, and the voice are Korean, and the bundle declares Korean as its only language — so a phone set to English still gets a Korean brief. Event and reminder titles carry through in whatever language they were typed, with 조사 (을/를, 이/가, 으로/로) attached at runtime from the pronunciation of the word in front of them, digits and Latin letters included.

The design problem

Speaking at 7am without a Shortcut

iOS gives no app a general “wake up at 7am and start talking” primitive. It gives three narrower mechanisms, each with a hole in it. The app uses all of them, arranged so each one covers the others’ gaps — the cheapest tier is also the most durable, and nothing above it is load-bearing.

  1. 01

    Tier 1 — the notification sound is the brief

    The brief is rendered to speech ahead of time with AVSpeechSynthesizer.write(_:toBufferCallback:), written to Library/Sounds as a .caf file, and attached as the sound of a repeating calendar notification. iOS plays it at the set time.

    Holds

    • Fires with the app force-quit, and after a reboot
    • Zero interaction

    Costs

    • iOS caps notification sounds at 30 seconds — the script is trimmed by whole time blocks, never mid-sentence
    • Obeys the ringer switch and Silent Mode, like any notification sound
  2. 02

    Tier 1b — a second, silent notification for the two lists

    확인이 필요한 일 and 이미 정리된 일 are read with the eyes, not the ears: titles, list names, and links turn into a wall of sound when spoken. They arrive a minute after the readout as a silent notification, counts in the body and on the app icon. Tapping it opens the app scrolled straight to those sections.

    Holds

    • Never chimes over the speech
    • Nothing pending, no notification

    Costs

    • Requires a tap — by design
  3. 03

    Tier 2 — hands-free full readout (opt-in)

    With Hands-free on, an AVAudioSession stays alive on a near-silent loop with UIBackgroundModes: audio declared, so iOS does not suspend the process and a timer can speak the entire first section. The alarm-clock pattern.

    Holds

    • The whole first section, not a 30-second trim
    • Works from the background and the lock screen

    Costs

    • Costs battery — which is why it is off by default
    • Does not survive a force-quit or a reboot; tier 1 covers those
  4. 04

    Tier 3 — tapping the notification

    Opening the readout notification, or its 읽어 주기 action, reads the time blocks immediately. The manual floor under the other three.

    Holds

    • Always available

    Costs

    • Requires the phone in hand

A background refresh task re-gathers the day and re-renders the audio a couple of hours before the readout. Background refresh is opportunistic — iOS decides whether it runs at all — which is exactly why tier 1 never depends on it. If the refresh does not run, the notification still fires with the last audio that was rendered.

Where the content comes from

The device, not a server

The browser version pulls from Gmail, Slack, and Google Calendar connectors. An iOS app cannot reach those without a server and an OAuth backend, so this reads the phone instead, through EventKit. No server, no account, nothing leaves the device.

Browser roleOn iOS
CalendarApple Calendar — today’s events, drawn and classified, plus tomorrow’s for context and prep
Email and chat asksApple Reminders — what is overdue or due today
ResolvedReminders completed in the last two days, plus meetings the organizer cancelled

One store class is the only place that touches EventKit, and one value type is the seam everything downstream depends on. If the connector sources ever become reachable, that is the single file that changes.

Optional prose pass

Letting a model write the sentences, narrowly

Off by default. The on-device builder decides what the brief says and where every fact came from; with a key in Settings, the day’s titles and times are sent to the Claude Messages API and replacement sentences come back in Korean 해요체, with the reader’s own event and task titles left untranslated.

The merge is deliberately narrow: only sentences and the headline are taken from the response, keyed by ids the app sent. Titles, links, source phrases, and structure always stay as built on device — so a bad response degrades the writing and cannot invent a task or a link. Gathered text is passed as data, with an explicit instruction that any command inside it is content, not direction. There is no official Swift SDK, so this is a direct HTTPS call, and the key lives in the Keychain rather than in UserDefaults.

Known limits

What it does not do

  • Notification sounds obey the ringer switch and Silent Mode, and 30 seconds is a hard iOS cap.
  • Hands-free does not survive a force-quit or a reboot, and costs battery.
  • Background refresh timing is at iOS’s discretion; nothing breaks when it does not run.
  • Reminders stand in for the browser version’s email and chat asks — there is no “have I already replied to this thread” check, because there is no thread to look at.
  • Korean only. Every generated sentence is written in Korean, so there is no second language to fall back to.

Source

Mobile Brief

The Xcode project is generated from an XcodeGen spec rather than committed, and two equivalent CI paths — Codemagic and GitHub Actions — ship to TestFlight with an App Store Connect API key, never an Apple ID password. Build notes, the signing walkthrough, and the full file layout are in the repository README.