| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1840217 commit 93d8ac9
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,7 +4,12 @@ | |||
| 4 | 4 | ||
| 5 | 5 | **Goal:** Allow users to send SMS at an exact time (bypassing scheduling) when `SendAt` is specified, and replace the 1-second bulk hack with rate-based dispatch delays. | |
| 6 | 6 | ||
| 7 | - **Architecture:** Add a transient `ExactSendTime` flag flowing through the event system. When true, bypass rate-limit and schedule window logic in notification scheduling. For bulk sends without explicit time, compute dispatch delay from `MessagesPerMinute` per-phone instead of hardcoded 1s. | ||
| 7 | + **Related docs:** | ||
| 8 | + | ||
| 9 | + - [Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages) — the existing `SendAt`/`SendTime` feature | ||
| 10 | + - [Control SMS Send Rate](https://docs.httpsms.com/features/control-sms-send-rate) — the existing `MessagesPerMinute` rate-limiting feature | ||
| 11 | + | ||
| 12 | + **Architecture:** Add a transient `ExactSendTime` flag flowing through the event system. When true, bypass [rate-limit](https://docs.httpsms.com/features/control-sms-send-rate) and schedule window logic in notification scheduling. For bulk sends without explicit time, compute dispatch delay from `MessagesPerMinute` per-phone instead of hardcoded 1s. | ||
| 8 | 13 | ||
| 9 | 14 | **Tech Stack:** Go, Fiber, GORM, CockroachDB, Google Cloud Tasks (CloudEvents) | |
| 10 | 15 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | ## Problem | |
| 4 | 4 | ||
| 5 | - The send schedule feature (and future features) need usage limits based on the user's subscription plan. Free users should be limited to 1 send schedule; paid users get unlimited. The system must be: | ||
| 5 | + The [MessageSendSchedule](./2026-05-03-scheduling-send-refactor-design.md#messagesendschedule-send-windows--new-feature) feature (and future features) need usage limits based on the user's subscription plan. Free users should be limited to 1 send schedule; paid users get unlimited. The system must be: | ||
| 6 | 6 | ||
| 7 | 7 | - **Scalable**: Easy to add new entity limits without architectural changes | |
| 8 | 8 | - **Configurable**: Disabled by default for self-hosted deployments, enabled via env var for cloud | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,19 +1,24 @@ | |||
| 1 | 1 | # Scheduling Send Refactor Design | |
| 2 | 2 | ||
| 3 | + ## Related Documentation | ||
| 4 | + | ||
| 5 | + - [Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages) — existing `SendAt`/`SendTime` scheduling feature | ||
| 6 | + - [Control SMS Send Rate](https://docs.httpsms.com/features/control-sms-send-rate) — existing `MessagesPerMinute` rate-limiting feature | ||
| 7 | + | ||
| 3 | 8 | ## Problem Statement | |
| 4 | 9 | ||
| 5 | 10 | The current SMS scheduling logic has two issues: | |
| 6 | 11 | ||
| 7 | - 1. **No way to send at an exact time without scheduling interference.** When a user specifies a `SendTime`/`SendAt`, the system still applies rate-limiting and schedule window logic, which may shift the actual send time. | ||
| 12 | + 1. **No way to send at an exact time without scheduling interference.** When a user specifies a `SendTime`/`SendAt` (see [Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages)), the system still applies rate-limiting and schedule window logic, which may shift the actual send time. | ||
| 8 | 13 | ||
| 9 | 14 | 2. **Bulk message contention.** When bulk messages (API or CSV) are sent, all events arrive at the Cloud Tasks queue near-simultaneously, causing DB serialization conflicts in `PhoneNotificationRepository.Schedule()` (which uses `SELECT ... ORDER BY scheduled_at DESC` in a transaction). The current workaround is a hardcoded 1-second spacing hack. | |
| 10 | 15 | ||
| 11 | 16 | ## Proposed Solution | |
| 12 | 17 | ||
| 13 | 18 | ### Core Principle | |
| 14 | 19 | ||
| 15 | - - **Explicit `SendTime`** = send at exactly that time, bypass all scheduling logic. | ||
| 16 | - - **No `SendTime`** = apply full scheduling logic (rate-limit + schedule windows), with rate-based Cloud Task dispatch delay to prevent DB contention. | ||
| 20 | + - **Explicit `SendTime`** = send at exactly that time, bypass all scheduling logic. See [Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages) for how `SendAt` works. | ||
| 21 | + - **No `SendTime`** = apply full scheduling logic ([rate-limit](https://docs.httpsms.com/features/control-sms-send-rate) + schedule windows), with rate-based Cloud Task dispatch delay to prevent DB contention. | ||
| 17 | 22 | ||
| 18 | 23 | ### Design | |
| 19 | 24 | ||
@@ -111,7 +116,73 @@ User sends request | |||
| 111 | 116 | ### What Does NOT Change | |
| 112 | 117 | ||
| 113 | 118 | - The `MessageSendSchedule` entity and its `ResolveScheduledAt()` logic | |
| 114 | - - The `SendScheduleService` CRUD operations | ||
| 119 | + - The `MessageSendScheduleService` CRUD operations | ||
| 115 | 120 | - The phone notification entity schema (no new DB columns) | |
| 116 | 121 | - The Android app behavior | |
| 117 | 122 | - The web frontend (models auto-generated from Swagger) | |
| 123 | + | ||
| 124 | + --- | ||
| 125 | + | ||
| 126 | + ## MessageSendSchedule (Send Windows) — New Feature | ||
| 127 | + | ||
| 128 | + This is the only scheduling mechanism that does **not** have a dedicated documentation page yet. Unlike [Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages) (one-time `SendAt`) and [Control SMS Send Rate](https://docs.httpsms.com/features/control-sms-send-rate) (`MessagesPerMinute` throttling), MessageSendSchedule defines **recurring availability windows** that control when a phone is allowed to send outgoing SMS messages. | ||
| 129 | + | ||
| 130 | + ### Concept | ||
| 131 | + | ||
| 132 | + A `MessageSendSchedule` is a named set of time windows (per day of week) that define when the phone can send. Messages arriving outside those windows are delayed until the next available window opens. | ||
| 133 | + | ||
| 134 | + ### Entity | ||
| 135 | + | ||
| 136 | + ```go | ||
| 137 | + type MessageSendSchedule struct { | ||
| 138 | + ID uuid.UUID | ||
| 139 | + UserID UserID | ||
| 140 | + Name string // e.g. "Business Hours" | ||
| 141 | + Timezone string // IANA timezone e.g. "Europe/Tallinn" | ||
| 142 | + IsActive bool | ||
| 143 | + Windows []MessageSendScheduleWindow // per-day availability slots | ||
| 144 | + CreatedAt time.Time | ||
| 145 | + UpdatedAt time.Time | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + type MessageSendScheduleWindow struct { | ||
| 149 | + DayOfWeek int // 0=Sunday, 6=Saturday | ||
| 150 | + StartMinute int // minutes from midnight (e.g. 540 = 9:00) | ||
| 151 | + EndMinute int // minutes from midnight (e.g. 1020 = 17:00) | ||
| 152 | + } | ||
| 153 | + ``` | ||
| 154 | + | ||
| 155 | + ### How It Works | ||
| 156 | + | ||
| 157 | + 1. A user creates a schedule via `POST /v1/send-schedules` with a name, timezone, and one or more windows. | ||
| 158 | + 2. The schedule is linked to a phone via a `ScheduleID` field on the phone entity. | ||
| 159 | + 3. When a message is queued (without an explicit `SendAt`), the `PhoneNotificationRepository.Schedule()` method calls `MessageSendSchedule.ResolveScheduledAt(now)` to find the next allowed send time. | ||
| 160 | + 4. If the current time falls within a window, the message sends immediately. If not, it's delayed to the start of the next available window. | ||
| 161 | + | ||
| 162 | + ### API Endpoints | ||
| 163 | + | ||
| 164 | + | Method | Endpoint | Description | | ||
| 165 | + | ------ | --------------------------------- | --------------------------- | | ||
| 166 | + | GET | `/v1/send-schedules` | List all user schedules | | ||
| 167 | + | POST | `/v1/send-schedules` | Create a new schedule | | ||
| 168 | + | PUT | `/v1/send-schedules/{scheduleID}` | Update an existing schedule | | ||
| 169 | + | DELETE | `/v1/send-schedules/{scheduleID}` | Delete a schedule | | ||
| 170 | + | ||
| 171 | + ### Validation Rules | ||
| 172 | + | ||
| 173 | + - `name`: required, 2–100 characters | ||
| 174 | + - `timezone`: required, valid IANA timezone | ||
| 175 | + - `windows[].day_of_week`: 0–6 | ||
| 176 | + - `windows[].start_minute`: 0–1439 | ||
| 177 | + - `windows[].end_minute`: 1–1440, must be greater than `start_minute` | ||
| 178 | + - Max 6 windows per day | ||
| 179 | + - No overlapping windows on the same day | ||
| 180 | + | ||
| 181 | + ### Entitlement | ||
| 182 | + | ||
| 183 | + Free users are limited to 1 schedule. Paid users get unlimited schedules. Enforced via `EntitlementService.Check()` in the handler before creation. | ||
| 184 | + | ||
| 185 | + ### Interaction with Other Scheduling Features | ||
| 186 | + | ||
| 187 | + - **[Scheduling SMS Messages](https://docs.httpsms.com/features/scheduling-sms-messages)** (`SendAt`): When provided, bypasses send windows entirely (exact send time). | ||
| 188 | + - **[Control SMS Send Rate](https://docs.httpsms.com/features/control-sms-send-rate)** (`MessagesPerMinute`): Applied independently — rate-limiting still applies within allowed windows. Both constraints compose: the message must be within a window AND respect the rate limit. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments