PageSourceSearch

https://ljefferson-menoko-site.netlify.app/assets/powalert-Cu0ZAYlQ.js

js ljefferson-menoko-site.netlify.app collected 2026-10-03 11:00:48 UTC 10,915 bytes, 1 lines download raw bytes

1const e="powalert",t="PowAlert",a="A real-time snow-alert backend that turns resort forecasts and personal thresholds into one clean notification per user per cycle, correctly segmented for SMS, email and push, delivered exactly once.",n="Production (live at powalert.com)",i="production",r="primary",s=["Node.js","Express","JavaScript (ESM)","MongoDB / Mongoose","node-cron","Twilio","SendGrid","Postmark","Expo Push","Visual Crossing API","Puppeteer","argon2","JWT","express-validator","Swagger / OpenAPI","Morgan","node-cache"],o=["event-driven backend design","multi-channel notification architecture","Facade and Strategy patterns","greedy message segmentation","idempotent writes and exactly-once delivery","scheduled job design","third-party API integration","password hashing with argon2","JWT authentication","API documentation with Swagger","centralized error handling"],d="/case-studies/art/powalert.webp",l=[{label:"powalert.com",href:"https://powalert.com"}],c=[{heading:"Problem",blocks:[{kind:"p",spans:[{text:"PowAlert notifies skiers when conditions at the resorts they care about cross the thresholds they set (snowfall, snow depth, storm timing) and links them straight to lodging so they can act on it. The entire product is the notification. Everything else is plumbing."}]},{kind:"p",spans:[{text:"Which means the failure modes are unusually unforgiving. An alert that arrives late is worthless, because the powder day is the thing being sold. An alert that arrives twice makes the product feel broken. An alert that arrives seven times in one morning (one message per triggered condition per resort per forecast day, which is what a naive implementation produces) gets the app deleted. And an alert truncated mid-sentence by a channel character limit is worse than no alert, because the user now knows something happened and cannot tell what."}]},{kind:"p",spans:[{text:"So the real problem is: take a stream of forecast data and each user's personal thresholds, and turn them into "},{text:"the right alerts, batched sensibly, formatted correctly for each channel, and delivered exactly once",bold:!0},{text:", on a schedule, without anyone watching."}]},{kind:"p",spans:[{text:'There is a structural complication underneath. The scheduler drives processing on a repeating cycle, and a cycle can overlap, retry, or re-run after a crash. That means the same forecast day for the same resort for the same user will be evaluated more than once. Anything that treats "I found an alert" as "send an alert" will cry wolf.'}]}]},{heading:"What I Built",blocks:[{kind:"p",spans:[{text:"I led the design and rebuild of the PowAlert backend (an Express service on Node) owning its architecture, the multi-channel notification engine, the alert-processing algorithms, and the scheduled ingestion jobs. The brief was to turn ad-hoc notification code into a maintainable, pattern-driven system that could absorb new delivery channels and new alerting rules without a rewrite each time."}]},{kind:"p",spans:[{text:"A modular Express service with real boundaries.",bold:!0},{text:" Six route modules (auth, users, resorts, Visual Crossing, notifications, Expedia links), seven Mongoose models, and a middleware stack handling request logging with Morgan, CORS with an environment-driven origin, validation, and centralized error handling composed after all routes. Routing, data processing, third-party integration, and notification delivery each live in their own modules with one responsibility apiece. Business handlers stay thin because the cross-cutting concerns are middleware, not copy-paste."}]},{kind:"p",spans:[{text:"The notification engine, built around the part most likely to change.",bold:!0},{text:" Delivery is the component that gets new requirements every quarter, so it got the most design attention. A "},{text:"facade",bold:!0},{text:" presents one unified interface: calling code asks to notify a user, and the facade coordinates SMS, email and push behind that single entry point. Adding a channel does not touch the alert logic. A "},{text:"strategy",bold:!0},{text:" per channel handles message segmentation, because SMS and email have different hard limits and different formatting rules, and each needs to be tuned independently, `splitAggregatedMessages` for SMS, `splitAggregatedEmailMessages` for email. Three live channels sit behind that facade: Twilio for SMS, SendGrid and Postmark for email, and Expo Push for mobile."}]},{kind:"p",spans:[{text:"Aggregation before delivery.",bold:!0},{text:" Rather than emitting one message per triggered condition, the pipeline walks every user with notifications enabled, resolves their preferred resorts, pulls the stored forecast for each, and accumulates every day where snowfall meets or exceeds that user's own `alertThreshold` into a per-user alert set. Those become a single aggregated notification, persisted as its own record linking the individual notification IDs 
1it covers. One user with four resorts and a three-day storm gets one message, not twelve."}]},{kind:"p",spans:[{text:"Greedy segmentation.",bold:!0},{text:" An aggregated alert can still exceed a channel's limit. I implemented a greedy segmentation algorithm that splits the aggregated message into the fewest segments that each respect the channel's limit while preserving readability, so a long alert is delivered in a valid format instead of being truncated or rejected by the carrier. Lodging links are added once per resort rather than once per forecast day, which is a small rule that removes a large amount of repetition from the final message."}]},{kind:"p",spans:[{text:"Idempotent, exactly-once delivery.",bold:!0},{text:" Because the cron cycle can evaluate the same alert more than once, every write goes through an idempotent upsert keyed so that a repeat evaluation updates the existing record rather than creating a second one. That single decision is the difference between a trustworthy alerting product and one that cries wolf, and it is why the system can be re-run safely after a failed cycle instead of requiring someone to reason about what already went out."}]},{kind:"p",spans:[{text:"Scheduled ingestion.",bold:!0},{text:" A node-cron job fetches fresh Visual Crossing forecast data for tracked resorts on a repeating cycle and stores it per resort, so the alert pipeline always evaluates current conditions and nothing requires a manual trigger. I evaluated multiple weather providers and selected Visual Crossing on integration quality, response customization, and cost-to-value. A Puppeteer-based resort scraper and seeding utilities keep the resort dataset current, and node-cache absorbs repeat reads inside a cycle."}]},{kind:"p",spans:[{text:"Auth, validation and documentation.",bold:!0},{text:' Passwords are hashed with argon2 rather than bcrypt. Sessions are JWT-based with auth middleware guarding protected routes. Request bodies are validated with express-validator at the edge so handlers can assume shape. The API is documented with Swagger/OpenAPI served at `/api-docs`, which made the frontend team self-service and removed a standing category of "what does this endpoint return" interruptions.'}]}]},{heading:"Skills Demonstrated",blocks:[{kind:"p",spans:[{text:"Event-driven backend design, Node.js, Express, JavaScript ESM, MongoDB and Mongoose, multi-channel notification architecture, Facade and Strategy patterns applied where change actually happens, message aggregation algorithms, greedy segmentation under hard channel limits, idempotent writes and exactly-once delivery semantics, scheduled job design with node-cron, third-party API integration and provider evaluation (Twilio, SendGrid, Postmark, Expo, Visual Crossing), Puppeteer scraping, argon2 password hashing, JWT authentication, request validation with express-validator, centralized error handling, HTTP request logging, in-process caching, Swagger and OpenAPI documentation"}]}]},{heading:"Outcome / Evidence",blocks:[{kind:"p",spans:[{text:"The rebuilt backend shipped as the engine behind PowAlert's launched MVP, live at "},{text:"powalert.com",href:"https://powalert.com"},{text:". The pattern-driven design paid off exactly where it was aimed: the notification system became a small, well-factored core that absorbed a third delivery channel and new alerting rules without a rewrite, and the aggregation, segmentation and idempotency work turned a potentially noisy, unreliable alert stream into single, correctly-formatted notifications delivered once."}]},{kind:"p",spans:[{text:"Concretely: six route modules, seven Mongoose models, three delivery channels behind one facade, two channel-specific segmentation strategies, cron-driven forecast ingestion, and idempotent upserts preventing duplicate alerts. Swagger UI served from the API itself. My engagement ran January to March 2025 as the back-end developer leading the rebuild."}]},{kind:"p",spans:[{text:"This write-up intentionally omits proprietary business logic, credentials, and internal implementation detail, it c
1overs the engineering problems and the patterns used to solve them."}]}]},{heading:"Interview Talking Points",blocks:[{kind:"list",items:[[{text:"The design attention went to the component most likely to change. Delivery channels churn; alert logic does not. A facade over delivery plus a per-channel segmentation strategy means adding push notifications was additive rather than invasive, and it was, later."}],[{text:"Aggregation is a product decision implemented as an algorithm. One message per triggered condition is technically correct and commercially fatal. Collecting every alert relevant to a user into one notification per cycle is the difference between a service and spam."}],[{text:'Idempotency is the load-bearing decision. A cron-driven pipeline will evaluate the same alert twice, after a crash, a retry, or an overlapping cycle. Upserts keyed on stable identity mean a re-run is safe, which in turn means the operational answer to a failed cycle is "run it again" rather than "reason carefully about what already went out."'}],[{text:"Greedy segmentation is the right shape for the constraint: minimize segment count subject to a hard per-channel limit, without breaking readability. A truncated alert is worse than no alert, because the user knows something happened and cannot tell what."}],[{text:"I chose the weather provider on integration quality and cost-to-value rather than on feature-list size. The forecast API is the one dependency the entire product rests on, so the selection criteria were about what happens when it degrades, not what it does when it works."}]]}]}],h={slug:e,title:t,oneLiner:a,status:n,statusFamily:i,tier:r,stack:s,skills:o,image:d,links:l,sections:c};export{h as default,d as image,l as links,a as oneLiner,c as sections,o as skills,e as slug,s as stack,n as status,i as statusFamily,r as tier,t as title};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.