
An iOS and macOS app that syncs tasks from Trello into Apple Reminders. A self-hosted sync broker running on any always-on Linux host receives Trello webhook events and queues changes for the app to pull.
Key features:
| Privacy Policy | Terms of Service |
The main view shows all synced reminder lists and items, with sync status displayed below the navigation title and any unresolved conflicts or potential duplicates listed as tappable rows. Tapping a task, conflict, or duplicate row opens its details in a stackable slide-up view (the same presentation style as Settings); task completion is still toggled by tapping the row’s checkbox. Pull down on the list to trigger a manual sync. Settings are accessible via the gear icon in the toolbar.
On macOS, DailyRoundup runs as a regular dock app with the same single-view layout as iOS — synced lists and tasks with inline sync status. A menu bar icon provides quick access to sync status, pending change count, conflict count, a Sync Now button, and Settings. The app syncs continuously in the background — it triggers a sync whenever Reminders changes, on a 60-second fallback timer, and immediately after the Mac wakes from sleep.
Start the server for development:
cd roundup-server
python -m dailyroundup.app
Start with gunicorn for production:
gunicorn "dailyroundup.app:create_app()" --bind 0.0.0.0:5000 --workers 4 --timeout 60
Requirements: Xcode 26+, iOS 26+, macOS 26+, an Apple Developer account
Clone the repository:
git clone git@github.com:dcwalker/DailyRoundup.git
cd DailyRoundup
Copy the Xcode config sample and add your development team ID:
cp Local.xcconfig.sample Local.xcconfig
# Edit Local.xcconfig and set DEVELOPMENT_TEAM to your Apple Developer Team ID
Open the project in Xcode and run on a device or simulator:
open DailyRoundup.xcodeproj
On first launch, grant Reminders and Notifications access when prompted.
Tap the gear icon, enter your server URL, and tap Connect to Server… to complete setup (see Connecting Trello).
Requirements: Python 3.12+
Clone the repository and change into this directory:
cd roundup-server
Install dependencies:
pip install -r requirements.txt
Copy env.sample to .env and fill in all required values:
cp env.sample .env
$EDITOR .env
Run database migrations:
python migrate.py
Start the server (see Usage).
The server must be reachable over HTTPS for Trello webhooks to fire. Use nginx or Caddy as a TLS-terminating reverse proxy in front of gunicorn.
Silent background-sync pushes are optional. The server starts and operates normally without them — devices fall back to polling whenever the app is opened. To enable near-real-time sync (Trello change → silent push → Reminders update within seconds):
.p8 file (it can only be downloaded once; store it securely on the server).env file (see Environment Variables):
APNS_KEY_ID and APNS_TEAM_ID from the values aboveAPNS_BUNDLE_ID: the app’s bundle ID (e.g. dev.dcwalker.DailyRoundup)APNS_PRIVATE_KEY: the full contents of the .p8 file, including the -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- linesSet APNS_USE_SANDBOX=1 if testing with a development or Ad Hoc build. Omit it (or set to 0) for App Store and TestFlight builds.
If any of the four required variables (APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, APNS_PRIVATE_KEY) are absent, APNs calls are silently skipped and a DEBUG-level log message is emitted.
The app connects to Trello via OAuth during initial setup. You will need a Trello API Key and Webhook Secret before you begin.
Get a Trello API Key from the Trello Power-Ups admin page. Create a new Power-Up (or use an existing one) and copy the API Key from its API Key tab.
Generate a Webhook Secret — any long random string. The server uses this to verify HMAC-SHA1 signatures on incoming Trello webhook events. You can generate one with:
openssl rand -hex 32
DAILYROUNDUP_BOOTSTRAP_TOKEN value from your server’s .env fileTap Connect. The app creates your account on the server and immediately opens a Trello OAuth page in an in-app browser. Sign in to Trello and tap Allow to grant DailyRoundup read/write access. The OAuth token is sent back to the app automatically and persisted on the server.
If you already have a permanent auth token (e.g. from a previous setup or another device), expand I already have a token in the connect sheet and paste the token directly. This skips the bootstrap token and OAuth steps.
To re-authorize Trello (e.g. if you revoked access), tap Authorize Trello in the Trello Configuration section of Settings. This opens the same OAuth flow and updates the token on the server.
For a detailed sequence diagram showing every API call during setup and what the server stores at each step, see Setup Flow Details.
Health monitoring via GET /health and GET /dailyroundup/status, optional Datadog usage metrics over DogStatsD, GitHub Actions alerting on deployment failures, common troubleshooting scenarios (auth errors, missing webhooks, APNs configuration), and server log access via journalctl. See Operations for full details.
The card-action scheduler (issue #334) is a background daemon thread inside roundup-server that drives the account-wide “move cards by date” feature:
due - offset (default offset: 2 hours) and is applied eagerly, driven by due-date changes (issue #404): adding a due date (or creating a card with one) sets “Remind me”; changing the due date moves it; removing the due date clears it. The value is not rewritten while the due date is unchanged, so a user who hand-edits or clears “Remind me” keeps that value until the due date next changes. Enabling the feature (a 0 → 1 flip of auto_set_remind_enabled) backfills “Remind me” for every existing due-dated card in a background job. The transition is detected for due-date changes from both Trello and Reminders, and propagates to both sides plus any Live Activity.The configuration is account-wide and applies to every synced list on the account’s trello_board_id; synced lists on any other board are skipped (with a WARNING log) because Trello’s update_card cannot cross boards. The two destination list IDs must reference open Trello lists on the same board.
The scheduler also drives the per-list pin to top feature, enabled with move_pinned_to_top_enabled. The 📌 prefix on a card’s name is the source of truth: when the toggle is on, cards whose name starts with 📌 are kept at the top of the Trello list, ordered by dateLastActivity descending, while non-pinned cards keep their relative order. Reordering is event-driven on Trello webhook events under the sync_lease, logging at INFO for each reorder (touched list, pinned-card count, and cards moved), with startup and daily reconcile passes that correct any drift left by missed webhook events and log at WARN when they do. A 📌 card without a due date can also surface on opted-in devices as a title-only Live Activity for a configurable window; a 📌 card that has a due date keeps the existing remind-to-due Live Activity.
The scheduler is event-driven: it maintains a card_action_index table populated from Trello webhook events and from configuration changes, sleeps until the earliest indexed wake-up moment, and acquires the global sync_lease before acting so concurrent writers cannot race. It also runs a reconciliation pass on startup and on a daily cadence that walks every applicable list, compares indexed timestamps against current Trello state, corrects any drift, and logs discrepancies at WARNING so missed webhook events stay visible to operators.
Configuration is stored on the accounts table and is set via PUT /dailyroundup/account. Set DAILYROUNDUP_SCHEDULER_ENABLED=0 to disable the scheduler.
System architecture diagrams, webhook ingest and foreground sync flow sequences, module structure tables for the iOS/macOS app and roundup-server, and the sixteen-table database schema. See Technical Details for full details.
| Variable | Required | Description |
|---|---|---|
DAILYROUNDUP_BOOTSTRAP_TOKEN |
Yes (initial setup) | One-time token used to create the first account via POST /dailyroundup/account. Once an account is created, the permanent per-account token is stored in iCloud Keychain and this variable is no longer needed. |
DAILYROUNDUP_WEBHOOK_URL |
Yes | Public HTTPS URL for the Trello webhook callback |
DAILYROUNDUP_DB_PATH |
No | Path to the SQLite database file (default: dailyroundup.db) |
DAILYROUNDUP_HOST |
No | Development server bind host (default: 0.0.0.0) |
DAILYROUNDUP_PORT |
No | Development server port (default: 5000) |
DAILYROUNDUP_MAX_CONTENT_LENGTH |
No | Maximum request body size in bytes (default: 8388608, i.e. 8 MB) |
DAILYROUNDUP_SCHEDULER_ENABLED |
No | Set to 0 to disable the card-action scheduler (default: enabled). See Card Action Scheduler. |
DAILYROUNDUP_DRAIN_TIMEOUT |
No | Seconds the deploy-drain step (drain.py) waits for in-flight sync jobs to finish before a restart (default: 30). |
FLASK_DEBUG |
No | Set to 1 for Flask debug mode (development only) |
APNS_KEY_ID |
No* | 10-character APNs key ID from the Apple Developer portal |
APNS_TEAM_ID |
No* | 10-character Team ID from the Apple Developer portal |
APNS_BUNDLE_ID |
No* | iOS/macOS app bundle ID (e.g. dev.dcwalker.DailyRoundup) |
APNS_PRIVATE_KEY |
No* | Full PEM contents of the .p8 file, including BEGIN/END PRIVATE KEY lines |
APNS_USE_SANDBOX |
No | Set to 1 to target the APNs sandbox; omit for production |
HEALTHCHECK_URL |
No | Base URL for the post-deployment health check script (default: http://127.0.0.1:5000) |
HEALTHCHECK_TIMEOUT |
No | Seconds to wait for the server to become ready during the health check (default: 30) |
HEALTHCHECK_TOKEN |
No | Per-account bearer token for the GET /dailyroundup/status check in healthcheck.py; if unset, that check is skipped |
DD_AGENT_HOST |
No | Datadog Agent host for DogStatsD usage metrics. When set, the server emits the counters described in Usage Metrics; when unset, metric emission is disabled and no socket is opened |
DD_DOGSTATSD_PORT |
No | DogStatsD UDP port (default: 8125); only used when DD_AGENT_HOST is set |
* All four APNS_* variables must be set together to enable push notifications. If any are absent, APNs is disabled and the server logs a DEBUG message. See Configuring APNs Push Notifications for setup steps.
Trello credentials (trello_api_key, trello_token, trello_board_id) and the Webhook Secret (trello_webhook_secret) are stored per-account in the database and configured via the app’s Settings screen after account creation.
POST /dailyroundup/account is protected by the DAILYROUNDUP_BOOTSTRAP_TOKEN environment variable. All other endpoints except GET /health and the Trello webhook endpoints require an Authorization header with the per-account bearer token returned when the account was created:
Authorization: Bearer <account-token>
See roundup-server/openapi.yaml for the full OpenAPI specification.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/dailyroundup/status |
Account-scoped operational stats (sync list count, registered device count) |
POST |
/dailyroundup/account |
Create a new account (requires bootstrap token) |
GET |
/dailyroundup/account |
Get current account details |
PUT |
/dailyroundup/account |
Update account settings (Trello credentials, webhook secret) |
GET |
/dailyroundup/lists |
List all synced list pairs for this account |
POST |
/dailyroundup/lists |
Create a new synced list pair |
POST |
/dailyroundup/lists/merge |
Merge an existing Trello list and Reminders calendar into a synced pair |
PATCH |
/dailyroundup/lists/{sync_list_id} |
Update list metadata |
DELETE |
/dailyroundup/lists/{sync_list_id} |
Remove a synced list pair |
POST |
/dailyroundup/lists/{sync_list_id}/reset |
Reset all sync state for a list |
GET |
/dailyroundup/trello_boards |
List all Trello boards on the configured account |
GET |
/dailyroundup/trello_lists |
List all Trello lists on the configured board |
GET |
/dailyroundup/trello_cards |
List all cards in a Trello list |
GET |
/dailyroundup/trello_test |
Test Trello API connectivity |
GET |
/dailyroundup/changes |
Return pending Trello-side changes |
POST |
/dailyroundup/changes/{change_id}/ack |
Acknowledge an applied change |
POST |
/dailyroundup/sync |
Accept Reminders-side changes (returns 202 with job_id for async polling) |
GET |
/dailyroundup/sync/jobs/{job_id} |
Poll for sync job status and results |
GET |
/dailyroundup/sync/status |
Return per-list sync statistics |
GET |
/dailyroundup/state |
Full state dump for initial sync or recovery |
GET |
/dailyroundup/card_metadata |
Fetch labels, attachments, checklists, and custom fields for cards |
POST |
/dailyroundup/devices |
Register a device APNs token |
POST |
/dailyroundup/reconcile |
Reconcile sync_tasks against actual Trello cards (open and archived); emits delete changes for cards that were permanently deleted |
GET |
/dailyroundup/potential-duplicates |
List unresolved potential duplicates |
POST |
/dailyroundup/potential-duplicates/{id}/resolve |
Resolve a potential duplicate (merge or keep_both) |
GET |
/dailyroundup/conflicts |
List all unresolved conflicts |
POST |
/dailyroundup/conflicts/{conflict_id}/resolve |
Resolve a conflict |
POST |
/dailyroundup/attachments/move-urls |
Batch-move URL link attachments into card descriptions and delete the attachments from Trello |
DELETE |
/dailyroundup/account |
Delete account and all associated data |
GET |
/dailyroundup/webhook/trello |
Trello webhook verification endpoint |
POST |
/dailyroundup/webhook/trello |
Receive Trello webhook events |
| Permission | Access granted | Required for |
|---|---|---|
| Reminders (EventKit) | Read and write all reminder lists and items | Displaying, creating, and updating reminders from Trello |
| iCloud Key-Value Store | Read and write app-specific KV pairs in iCloud | Syncing server URL, sync list IDs, and last sync timestamp across devices |
| iCloud Keychain | Read and write a single Keychain item | Storing the auth token securely across devices |
| Push Notifications (APNs) | Receive remote notifications and notification actions | Registering device tokens and delivering conflict-resolution alerts |
The app follows Apple’s Human Interface Guidelines on both iOS and macOS. There are no custom colors, typefaces, or animation overrides — all controls use standard SwiftUI system components so the app adapts automatically to light/dark mode, Dynamic Type, and accessibility settings. On macOS, Forms use .formStyle(.grouped) for consistent grouped-section layout. New UI should use system-provided components and avoid hardcoded colors or custom interaction patterns unless a standard component cannot fulfill the requirement.
.xcstrings catalogs are used and how to add new stringsSee CONTRIBUTING.md for coding standards, commit message format, and documentation guidelines. See AGENTS.md for AI-agent-specific directives.