Why I Built a Unified Inbox for Mac
I was context-switching 50+ times a day between Gmail, Slack, and GitHub. Each notification would pull me out of flow, and important messages got buried across multiple apps.
After trying every productivity app on the market (Shift, Wavebox, Rambox), I realized they all had the same fundamental problem: they're Electron apps that just wrap web pages. Heavy, slow, and not truly native to macOS.
So I built HeyRobyn - a native SwiftUI unified inbox for Mac.
Technical Decisions
Why SwiftUI Over Electron?
The performance difference is night and day:
- Memory: 150MB vs 800MB+ for Electron competitors
- Battery: Native APIs mean better power efficiency
- Speed: No web view overhead - instant UI updates
- Mac-native feel: Proper keyboard shortcuts, Touch Bar support, macOS design patterns
Architecture Overview
┌─────────────────────┐
│ SwiftUI Views │ ← Native UI layer
├─────────────────────┤
│ Core Data Models │ ← Local-first storage
├─────────────────────┤
│ API Integrations │ ← Gmail, Slack, GitHub SDKs
│ (OAuth2 + PKCE) │
└─────────────────────┘
Everything runs on-device. No cloud sync means:
- Privacy: Your emails never touch our servers
- Speed: No network latency for UI operations
- Offline: Full functionality without internet
The Hardest Part: OAuth2 Flow
Getting OAuth2 working natively in SwiftUI (without a web view) was surprisingly complex. Here's the key pattern I landed on:
// Simplified OAuth2 PKCE flow
class AuthManager: ObservableObject {
@Published var isAuthenticated = false
func authenticate(service: Service) async throws {
// 1. Generate PKCE challenge
let codeVerifier = generateCodeVerifier()
let codeChallenge = generateChallenge(from: codeVerifier)
// 2. Open system browser for consent
let authURL = buildAuthURL(challenge: codeChallenge)
NSWorkspace.shared.open(authURL)
// 3. Listen for callback via custom URL scheme
let authCode = try await waitForCallback()
// 4. Exchange code for tokens
let tokens = try await exchangeCodeForTokens(
code: authCode,
verifier: codeVerifier
)
// 5. Store securely in Keychain
try KeychainService.store(tokens, for: service)
isAuthenticated = true
}
}
This pattern works for Gmail (Google OAuth), Slack, and GitHub.
Unified Inbox Logic
The core challenge: how do you merge 3 different message types into one chronological feed?
I created a protocol-oriented approach:
protocol UnifiedMessage: Identifiable {
var timestamp: Date { get }
var sender: String { get }
var preview: String { get }
var source: MessageSource { get } // .email, .slack, .github
var priority: Priority { get }
}
// Each integration implements this protocol
struct EmailMessage: UnifiedMessage { ... }
struct SlackMessage: UnifiedMessage { ... }
struct GitHubNotification: UnifiedMessage { ... }
// Unified feed is just a sorted array
var feed: [UnifiedMessage] = [
emails, slackMessages, githubNotifications
].flatMap { $0 }
.sorted { $0.timestamp > $1.timestamp }
AI Triage: The Secret Sauce
I integrated local ML models (Core ML) to auto-categorize and prioritize messages:
- Urgent: Mentions from your manager, security alerts, build failures
- Important: PR reviews, direct messages, project updates
- Low priority: Marketing emails, automated notifications, FYIs
This runs entirely on-device using Apple's Natural Language framework:
import NaturalLanguage
func categorizePriority(for message: UnifiedMessage) -> Priority {
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = message.content
// Check for urgency keywords
if message.content.containsUrgentKeywords() {
return .urgent
}
// Analyze sender importance
if importantContacts.contains(message.sender) {
return .important
}
return .low
}
Lessons Learned
1. SwiftUI is Amazing (But Has Sharp Edges)
-
List performance: For 1000+ items, use
LazyVStack+ custom diffing, notList -
State management: Combine's
@Published+ObservableObjectworks great - Navigation: SwiftUI's NavigationStack is still buggy - sometimes need UIKit bridges
2. Privacy-First Is Hard But Worth It
Users are extremely sensitive about email privacy. Going 100% local-first was the right call:
- No backend to maintain or secure
- No GDPR compliance headaches
- Users trust the app more (visible in feedback)
3. Distribution on Mac Is Unique
- App Store: 30% fee + sandboxing restrictions (can't access Mail.app data)
- Direct distribution: Code signing costs $99/year, notarization takes time
- Pricing: Mac users expect either $10 one-time OR $5-15/mo subscription
I went with direct distribution + subscription ($12.50/mo early access).
What's Next
I'm launching HeyRobyn publicly on March 18, 2026. Early access waitlist: heyrobyn.ai
Planning to add:
- Linear integration (project management)
- Discord (for open source communities)
- Custom integrations via plugin API
Would love feedback from the community:
- What other integrations would you want?
- Any concerns about the privacy model?
- Mac developers: what's your unified inbox setup?
Tech stack: SwiftUI, Core Data, Combine, Core ML, OAuth2 PKCE
Waitlist: https://heyrobyn.ai
Top comments (0)