## Context docs/PRODUCT-MODEL.md established: SMS is the only real variable cost (~6¢ prepaid), plans meter it (10 SMS/yr per tag, family pools), and the alert SMS should carry a short link to a map page rather than raw coordinates. This change implements metering + the location-link flow. The existing throttles (250 m/10 min, fingerprint 24 h, daily/IP caps) remain — metering is an additional, separate gate. ## Goals / Non-Goals **Goals:** - Metered tags stop sending when credits exhaust (scan recording continues). - Every alert-with-location SMS carries a short URL to a map page. - Admin can set/refill credits (Filament field) and see usage. **Non-Goals:** - Renewal/expiry automation + unlock messaging ("pay to renew") — next slice (needs email infra). - Family pooled SMS across accounts — later (per-tag metering now). - Top-up purchases (Stripe) — manual admin top-ups now. - i18n on the location page — later. ## Decisions 1. **Metering = per-tag, `0 = unmetered`.** `tags.sms_allocated/used/period_start` (idempotent). `sms_allocated = 0` skips credit checks → existing/transitional tags unaffected until an admin assigns a plan. `sms_period_start` records the anchor for the (future) renewal job. 2. **Count only successful sends.** `alertOwner` and the finder-contact send each increment `sms_used` only after the sender returns success. Both count (per the product model: contact SMS uses the same meter). 3. **Short codes**: 6-char base36 random, unique (`url_shortened.code` PK); created at alert time when location is present; maps `code → scan_id`. `GET /s/{code}` loads the scan and renders the location page. 4. **SMS message**: with location, replace the raw maps URL with `where-woof.com/s/{code}` (short link). Without location, message unchanged (no link). 5. **Location page** (`location.html` template set): Google Maps embed iframe (`https://www.google.com/maps?q={lat},{lng}&output=embed`), finder phone if present, tag-page link, scan timestamp. Private by unguessable code. 6. **Admin**: TagResource — `sms_allocated` (numeric, editable = top-up), `sms_used` (view-only), `sms_period_start` (view). Eloquent already maps the new columns (fillable + cast). ## Risks / Trade-offs - [Unmetered default means metering isn't enforced until admin configures] → intentional transitional state; the mechanism + admin field ship now, plan assignment comes with the billing slice. - [Short code collision] → retry loop on insert (regenerate up to 3×). - [Location page exposes finder location to anyone with the code] → codes are 6-char random (unguessable); validity window considered a later concern. ## Migration Plan 1. `make db-up` (ALTERs + `url_shortened` table). 2. sqlc queries; Go handlers; template; admin field. 3. Verify (new suite + regressions); deploy (frontend binary + restart; admin rebuild). ## Open Questions - Whether contact-SMS should also get the short link — decided yes for consistency once the page exists (it shows the same location).