Case-studies

Building a Personalized Daily Horoscope Mobile App: A Technical Walkthrough

Full technical blueprint for a personalized daily horoscope mobile app at 10k DAU. Architecture, endpoint sequence, code samples, cost math, latency budget.

OK

Oleg Kopachovets

CTO & Co-Founder

May 9, 2026
6 min read
237 views
Architecture diagram showing daily horoscope mobile app flow
Architecture diagram showing daily horoscope mobile app flow
0%
Note on this walkthrough. This is an aspirational technical blueprint, not a published customer case. It is the architecture we recommend when a developer messages support asking "how would I build this?" The patterns are validated by our own usage logs across many mobile-app customers, but the numbers and code below are illustrative — your real-world costs and latencies will depend on your traffic shape, geography, and platform choices.

You're building a daily personalized horoscope app. Users sign up with birth data, get one tailored daily reading delivered as a push notification at their local morning time, and can tap in for "what's happening this week" and "year ahead" details. Target: 10,000 daily active users in year one, sustainable on a single founder's budget.

Here's the full blueprint.

Architecture in plain words

Three moving parts:

  1. Mobile client — React Native, Swift, or Kotlin. Renders the horoscope, manages the sign-up flow, handles push notifications, caches data locally.
  2. Your backend — one small server (Vercel, Railway, Fly.io, or Cloudflare Workers). Stores user accounts, holds API keys, runs the daily cron job that pre-generates horoscopes.
  3. Astrology API — our endpoints. You never call us directly from the mobile client (that would leak your API key); you call us from your backend.

Data flow:

javascript
1User opens app for first time
2 → Mobile client collects birth date, time, place
3 → POSTs to your backend (signup endpoint)
4 → Backend calls /p/natal-api once, stores result in your DB
5 → User is now "set up"
6
7Every day at 03:00 UTC
8 → Backend cron job runs
9 → For each active user: backend calls /p/personalized-horoscopes-api
10 → Result stored in DB keyed by user_id + date
11 → Backend sends push notification at user's local 08:00
12
13User taps notification
14 → Mobile client fetches today's horoscope from your DB (already cached)
15 → Renders it instantly
The key insight: users never wait for our API. They wait for your database, which is fast. The astrology API runs on your schedule, not on their tap.

The endpoint sequence

Step 1: Sign-up (one-time per user)

typescript
1// On your backend, called when user submits birth data
2async function onUserSignup(userId: string, birthData: BirthData) {
3 const natal = await fetch('https://api.astrology-api.io/v1/natal', {
4 method: 'POST',
5 headers: { 'Authorization': `Bearer ${process.env.ASTROLOGY_API_KEY}` },
6 body: JSON.stringify({
7 date: birthData.date, // e.g. "1990-04-15"
8 time: birthData.time, // e.g. "14:30"
9 latitude: birthData.lat, // e.g. 40.7128
10 longitude: birthData.lon, // e.g. -74.0060
11 timezone: birthData.tz, // e.g. "America/New_York"
12 }),
13 }).then(r => r.json());
14
15 await db.users.update(userId, {
16 natalChart: natal,
17 natalCachedAt: new Date(),
18 });
19}

This call runs once per user, ever. The chart never changes — your natal placements at birth are fixed.

Step 2: Daily cron (once per user per day)

typescript
1// Backend cron, runs at 03:00 UTC every day
2async function generateDailyHoroscopes() {
3 const activeUsers = await db.users.findActive();
4
5 // Bulk-batch in groups of 100 to use the bulk endpoint
6 for (const batch of chunk(activeUsers, 100)) {
7 const responses = await fetch('https://api.astrology-api.io/v1/personalized-horoscopes/bulk', {
8 method: 'POST',
9 headers: { 'Authorization': `Bearer ${process.env.ASTROLOGY_API_KEY}` },
10 body: JSON.stringify({
11 charts: batch.map(u => ({ userId: u.id, natalChart: u.natalChart })),
12 date: new Date().toISOString().slice(0, 10),
13 language: 'en',
14 }),
15 }).then(r => r.json());
16
17 for (const horoscope of responses.results) {
18 await db.dailyHoroscopes.upsert({
19 userId: horoscope.userId,
20 date: horoscope.date,
21 content: horoscope.content,
22 });
23 }
24 }
25}

100 charts in one request reduces network overhead. At 10,000 active users that's 100 API calls per day total, not 10,000.

Step 3: Push notification

typescript
1// Backend, runs continuously, watches for users whose local 08:00 has arrived
2async function dispatchMorningNotifications() {
3 const users = await db.users.findReadyForMorningPush();
4 for (const user of users) {
5 const today = await db.dailyHoroscopes.findOne({ userId: user.id, date: today() });
6 await pushService.send(user.deviceToken, {
7 title: 'Your daily horoscope',
8 body: today.content.headline,
9 data: { horoscopeId: today.id },
10 });
11 }
12}

Step 4: Mobile client fetches from your DB

swift
1// Swift, iOS client
2func fetchTodayHoroscope() async throws -> Horoscope {
3 let url = URL(string: "https://your-backend.com/api/horoscope/today")!
4 var request = URLRequest(url: url)
5 request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
6 let (data, _) = try await URLSession.shared.data(for: request)
7 return try JSONDecoder().decode(Horoscope.self, from: data)
8}
kotlin
1// Kotlin, Android client
2suspend fun fetchTodayHoroscope(): Horoscope {
3 val response = httpClient.get("https://your-backend.com/api/horoscope/today") {
4 headers { append("Authorization", "Bearer $authToken") }
5 }
6 return response.body()
7}

The mobile client never touches the astrology API directly. It only ever talks to your backend, which serves pre-computed daily content from your database.

Cost math at 10,000 DAU

Assumptions: 10,000 daily active users, 30 days/month, daily horoscope per user, plus occasional on-demand transit lookups (let's say 20% of users tap the "what's happening this week" tab on any given day).

API requests per month:
  • Sign-up calls (natal): negligible after onboarding ramp. Say 500 new users a month = 500 calls.
  • Daily horoscopes (bulk, 100 per request): 10,000 users × 30 days Ă· 100 batch size = 3,000 requests.
  • On-demand transits: 10,000 × 20% × 30 days = 60,000 individual requests. With aggressive caching on your backend (6-hour TTL), realistic API calls drop to about 10,000.
Total: ~13,500 requests/month.

That fits inside our Professional tier ($37/month, 55,000 requests). You have headroom for growth to roughly 35,000 DAU before the Business tier ($99/month, 220,000 requests) becomes necessary.

Real-world adjustment: add 10-20% buffer for retries, debug calls, and the occasional cron job failure that triggers re-runs. Plan for the next tier up by month nine.
See /pricing for the current tier table.

Latency budget

End-to-end latency the user sees, when they tap the morning notification:

StepTime
Notification tap → app openPlatform-dependent (~200ms iOS, ~300ms Android)
App → your backend (HTTPS round trip)50-150ms
Your backend → DB read5-20ms
Backend → app response50-150ms
Render16-50ms
Total user-perceived latency~400-600ms
The astrology API is not in this critical path. Daily horoscope content is already in your DB by the time the user wakes up. The only time our API touches user-perceived latency is during the initial sign-up flow, which is a one-time cost.

What can go wrong (and how to handle it)

  1. The daily cron job fails partway through. Wrap each batch in a try/catch, log failures with user IDs, and re-run failed batches in a separate retry job. Bulk endpoint failures are usually transient.
  2. A user's birth time is missing. The personalized horoscope endpoint accepts unknown time inputs and returns what's possible without it. Display a UI hint asking them to add birth time later for more accuracy.
  3. A user travels across time zones. Decide once: does "morning" mean local time at registration or current location? We recommend current location — store a device-reported timezone on every app open and update the push scheduling accordingly.
  4. You hit the request limit mid-month. The dashboard sends an alert at 80% utilization. Upgrade tier before that hits and your service never goes down.

Wrap-up

10,000 DAU on $37/month of API spend is a real, achievable target if you cache the natal chart once, batch the daily cron through the bulk endpoint, and never call the astrology API from the mobile client directly. The architecture is small enough to fit on a single Vercel project. Build the sign-up flow first, layer the cron job second, ship the notification layer last.

If you want to validate the request counts before writing code, the free tier (50 requests/month) is enough to run the sign-up flow for a handful of test users and see actual response shapes.

Oleg Kopachovets

Oleg Kopachovets

CTO & Co-Founder

Technical founder at Astrology API, specializing in astronomical calculations and AI-powered astrology