references/example.graph.json
references/example.graph.jsonBrowse 5 files
4,626 tokens
17,814 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1{2 "schemaVersion": "0.1.1",3 "kind": "graph",4 "generatedAt": "2026-08-19T18:24:00.000Z",5 "title": "Batch broadcast sending through Postmark",6 "summary": "Broadcast delivery moves from one Postmark request per recipient to batched requests of 500, with suppression filtering pulled in front of the send and the payload builder extracted into a shared library.",7 "lenses": [8 "architecture",9 "data-flow"10 ],11 "provenance": {12 "repo": {13 "owner": "ohansemmanuel",14 "name": "bestregards",15 "host": "github.com"16 },17 "base": {18 "sha": "3f5c1ab9d24e7f08c6b1a5d3e9074c2b8a6f1d40",19 "ref": "main"20 },21 "head": {22 "sha": "b71e0d4c8a92f5361de7c0b4a8f2593d6c1e8a77",23 "ref": "batch-broadcast-send"24 },25 "pullRequest": {26 "number": 128,27 "title": "Send broadcasts in batches of 500",28 "url": "https://github.com/ohansemmanuel/bestregards/pull/128"29 },30 "generator": {31 "name": "pr-lens-examples",32 "version": "0.1.0"33 }34 },35 "lanes": [36 {37 "id": "web",38 "label": "Next.js",39 "subtitle": "Vercel",40 "order": 041 },42 {43 "id": "functions",44 "label": "Cloud Functions",45 "subtitle": "Firebase",46 "order": 147 },48 {49 "id": "external",50 "label": "External",51 "subtitle": "Postmark",52 "order": 253 }54 ],55 "nodes": [56 {57 "id": "broadcast-composer",58 "label": "Broadcast composer",59 "kind": "ui",60 "delta": "unchanged",61 "lane": "web",62 "subtitle": "app/broadcasts/new",63 "summary": "Where an author writes a broadcast and hits send. Untouched by this change.",64 "files": [65 {66 "path": "app/broadcasts/new/page.tsx"67 }68 ],69 "badges": []70 },71 {72 "id": "queue-route",73 "label": "POST /api/broadcasts/queue",74 "kind": "route",75 "delta": "modified",76 "lane": "web",77 "summary": "Writes the queue document. Now stamps the recipient count and batch size the sender will use instead of leaving batching to the worker.",78 "files": [79 {80 "path": "app/api/broadcasts/queue/route.ts",81 "startLine": 24,82 "endLine": 9683 }84 ],85 "badges": [86 "+38 / -12"87 ]88 },89 {90 "id": "broadcast-queue",91 "label": "broadcastQueue",92 "kind": "datastore",93 "delta": "modified",94 "lane": "functions",95 "subtitle": "Firestore collection",96 "summary": "Queue documents gained batchSize and suppressedCount fields, and results are now written back per batch rather than per recipient.",97 "files": [98 {99 "path": "functions/src/broadcast/schema.ts",100 "startLine": 12,101 "endLine": 48102 }103 ],104 "badges": []105 },106 {107 "id": "send-broadcast-bulk",108 "label": "sendBroadcastBulk",109 "kind": "function",110 "delta": "added",111 "lane": "functions",112 "subtitle": "onWrite trigger",113 "summary": "New trigger handler. Fetches suppressions once, builds batched payloads, and posts them to Postmark in chunks of 500.",114 "files": [115 {116 "path": "functions/src/broadcast/sendBroadcastBulk.ts",117 "startLine": 1,118 "endLine": 142119 }120 ],121 "badges": [122 "new"123 ]124 },125 {126 "id": "build-bulk-payload",127 "label": "buildBulkPayload",128 "kind": "function",129 "delta": "added",130 "lane": "functions",131 "summary": "Turns a broadcast and its recipient slice into a Postmark batch request body.",132 "files": [133 {134 "path": "packages/broadcast-lib/src/buildBulkPayload.ts",135 "startLine": 1,136 "endLine": 74137 }138 ],139 "badges": []140 },141 {142 "id": "get-suppressed-emails",143 "label": "getSuppressedEmails",144 "kind": "function",145 "delta": "added",146 "lane": "functions",147 "summary": "Pulls the Postmark suppression dump once per broadcast so suppressed addresses are filtered before any batch is sent.",148 "files": [149 {150 "path": "packages/broadcast-lib/src/getSuppressedEmails.ts",151 "startLine": 1,152 "endLine": 58153 }154 ],155 "badges": []156 },157 {158 "id": "broadcast-lib",159 "label": "broadcast-lib",160 "kind": "package",161 "delta": "added",162 "lane": "functions",163 "subtitle": "packages/broadcast-lib",164 "summary": "New shared package so the queue route and the sender agree on payload shape and batch size.",165 "files": [166 {167 "path": "packages/broadcast-lib/src/index.ts"168 }169 ],170 "badges": [171 "new package"172 ]173 },174 {175 "id": "process-broadcast",176 "label": "processBroadcast",177 "kind": "function",178 "delta": "removed",179 "lane": "functions",180 "subtitle": "onWrite trigger",181 "summary": "The per-recipient loop this change replaces.",182 "files": [183 {184 "path": "functions/src/broadcast/processBroadcast.ts",185 "startLine": 1,186 "endLine": 118,187 "revision": "base"188 }189 ],190 "badges": []191 },192 {193 "id": "send-single-email",194 "label": "sendSingleEmail",195 "kind": "function",196 "delta": "removed",197 "lane": "functions",198 "summary": "One Postmark request per recipient. Gone with the loop that called it.",199 "files": [200 {201 "path": "functions/src/broadcast/sendSingleEmail.ts",202 "startLine": 1,203 "endLine": 46,204 "revision": "base"205 }206 ],207 "badges": []208 },209 {210 "id": "postmark",211 "label": "Postmark",212 "kind": "external",213 "delta": "modified",214 "lane": "external",215 "subtitle": "Email API",216 "summary": "Same provider, different endpoints: the batch endpoint and the suppression dump replace repeated single sends.",217 "files": [],218 "badges": []219 }220 ],221 "edges": [222 {223 "id": "composer-to-queue",224 "from": "broadcast-composer",225 "to": "queue-route",226 "kind": "http",227 "delta": "unchanged",228 "label": "send broadcast",229 "emphasis": "normal",230 "animated": false,231 "files": []232 },233 {234 "id": "queue-to-firestore",235 "from": "queue-route",236 "to": "broadcast-queue",237 "kind": "data",238 "delta": "modified",239 "label": "enqueue job",240 "emphasis": "normal",241 "animated": false,242 "files": []243 },244 {245 "id": "queue-to-lib",246 "from": "queue-route",247 "to": "broadcast-lib",248 "kind": "dependency",249 "delta": "added",250 "label": "batch size",251 "emphasis": "normal",252 "animated": false,253 "files": []254 },255 {256 "id": "firestore-to-bulk",257 "from": "broadcast-queue",258 "to": "send-broadcast-bulk",259 "kind": "event",260 "delta": "added",261 "label": "onWrite",262 "emphasis": "normal",263 "animated": false,264 "files": []265 },266 {267 "id": "firestore-to-process",268 "from": "broadcast-queue",269 "to": "process-broadcast",270 "kind": "event",271 "delta": "removed",272 "label": "onWrite",273 "emphasis": "normal",274 "animated": false,275 "files": []276 },277 {278 "id": "process-to-single",279 "from": "process-broadcast",280 "to": "send-single-email",281 "kind": "call",282 "delta": "removed",283 "label": "per recipient",284 "emphasis": "normal",285 "animated": false,286 "files": []287 },288 {289 "id": "single-to-postmark",290 "from": "send-single-email",291 "to": "postmark",292 "kind": "http",293 "delta": "removed",294 "label": "POST /email · 1 msg/call",295 "emphasis": "normal",296 "animated": false,297 "files": []298 },299 {300 "id": "bulk-to-payload",301 "from": "send-broadcast-bulk",302 "to": "build-bulk-payload",303 "kind": "call",304 "delta": "added",305 "emphasis": "normal",306 "animated": false,307 "files": []308 },309 {310 "id": "bulk-to-suppressions",311 "from": "send-broadcast-bulk",312 "to": "get-suppressed-emails",313 "kind": "call",314 "delta": "added",315 "emphasis": "normal",316 "animated": false,317 "files": []318 },319 {320 "id": "bulk-to-lib",321 "from": "send-broadcast-bulk",322 "to": "broadcast-lib",323 "kind": "dependency",324 "delta": "added",325 "emphasis": "normal",326 "animated": false,327 "files": []328 },329 {330 "id": "suppressions-to-postmark",331 "from": "get-suppressed-emails",332 "to": "postmark",333 "kind": "http",334 "delta": "added",335 "label": "GET suppression dump",336 "emphasis": "normal",337 "animated": true,338 "files": []339 },340 {341 "id": "bulk-to-postmark",342 "from": "send-broadcast-bulk",343 "to": "postmark",344 "kind": "http",345 "delta": "added",346 "label": "500 msgs/call",347 "emphasis": "hero",348 "animated": true,349 "summary": "The change in one edge: a broadcast to 10,000 recipients drops from 10,000 requests to 20.",350 "files": []351 },352 {353 "id": "bulk-to-firestore",354 "from": "send-broadcast-bulk",355 "to": "broadcast-queue",356 "kind": "data",357 "delta": "added",358 "label": "write results",359 "emphasis": "normal",360 "animated": false,361 "files": []362 }363 ],364 "flows": [365 {366 "id": "send-pipeline",367 "title": "Sending a broadcast",368 "summary": "The path a queued broadcast takes now, from enqueue to per-message results.",369 "delta": "modified",370 "participants": [371 {372 "node": "queue-route",373 "label": "queue route"374 },375 {376 "node": "broadcast-queue",377 "label": "Firestore"378 },379 {380 "node": "send-broadcast-bulk",381 "label": "sendBroadcastBulk"382 },383 {384 "node": "postmark",385 "label": "Postmark"386 }387 ],388 "messages": [389 {390 "id": "enqueue",391 "from": "queue-route",392 "to": "broadcast-queue",393 "label": "enqueue broadcast job",394 "kind": "async",395 "delta": "modified",396 "animated": true,397 "files": []398 },399 {400 "id": "trigger",401 "from": "broadcast-queue",402 "to": "send-broadcast-bulk",403 "label": "onWrite trigger",404 "kind": "async",405 "delta": "added",406 "animated": true,407 "files": []408 },409 {410 "id": "suppressions-request",411 "from": "send-broadcast-bulk",412 "to": "postmark",413 "label": "GET suppression dump",414 "kind": "sync",415 "delta": "added",416 "animated": true,417 "files": []418 },419 {420 "id": "suppressions-response",421 "from": "postmark",422 "to": "send-broadcast-bulk",423 "label": "suppressed addresses",424 "kind": "return",425 "delta": "added",426 "animated": true,427 "note": "Fetched once per broadcast, not once per recipient.",428 "files": []429 },430 {431 "id": "batch-post",432 "from": "send-broadcast-bulk",433 "to": "postmark",434 "label": "POST /email/batch · 500 msgs",435 "kind": "sync",436 "delta": "added",437 "animated": true,438 "repeat": 4,439 "note": "One request per 500 recipients; four for this 2,000-recipient broadcast.",440 "files": []441 },442 {443 "id": "batch-results",444 "from": "postmark",445 "to": "send-broadcast-bulk",446 "label": "per-message results",447 "kind": "return",448 "delta": "added",449 "animated": true,450 "files": []451 },452 {453 "id": "write-results",454 "from": "send-broadcast-bulk",455 "to": "broadcast-queue",456 "label": "write results",457 "kind": "async",458 "delta": "added",459 "animated": true,460 "files": []461 }462 ]463 }464 ],465 "stats": {466 "filesChanged": 14,467 "additions": 486,468 "deletions": 212,469 "chips": [470 {471 "label": "Postmark calls",472 "value": "500× fewer",473 "tone": "hero"474 },475 {476 "label": "New",477 "value": "4 units",478 "tone": "added"479 },480 {481 "label": "Retired",482 "value": "2 units",483 "tone": "removed"484 }485 ]486 },487 "views": [488 {489 "id": "overview",490 "title": "Architecture — blast radius",491 "lens": "architecture",492 "summary": "Everything this change touches, across all three lanes.",493 "scope": {494 "kind": "all"495 },496 "defaultOpen": true,497 "children": [498 {499 "id": "new-batch-path",500 "title": "The new batch path",501 "lens": "architecture",502 "summary": "What replaced the per-recipient loop.",503 "scope": {504 "kind": "selection",505 "lanes": [],506 "nodes": [507 "send-broadcast-bulk",508 "build-bulk-payload",509 "get-suppressed-emails",510 "broadcast-lib",511 "postmark"512 ],513 "edges": [514 "bulk-to-payload",515 "bulk-to-suppressions",516 "bulk-to-lib",517 "suppressions-to-postmark",518 "bulk-to-postmark",519 "bulk-to-firestore"520 ],521 "flows": []522 },523 "defaultOpen": false,524 "children": []525 },526 {527 "id": "retired-path",528 "title": "What was retired",529 "lens": "architecture",530 "summary": "The single-send path, kept visible so a reviewer can confirm nothing else called it.",531 "scope": {532 "kind": "selection",533 "lanes": [],534 "nodes": [535 "process-broadcast",536 "send-single-email"537 ],538 "edges": [539 "firestore-to-process",540 "process-to-single",541 "single-to-postmark"542 ],543 "flows": []544 },545 "defaultOpen": false,546 "children": []547 }548 ]549 },550 {551 "id": "send-pipeline-view",552 "title": "Data flow — sending a broadcast",553 "lens": "data-flow",554 "scope": {555 "kind": "selection",556 "lanes": [],557 "nodes": [],558 "edges": [],559 "flows": [560 "send-pipeline"561 ]562 },563 "defaultOpen": false,564 "children": []565 }566 ],567 "walkthrough": {568 "steps": [569 {570 "id": "batches-of-500",571 "heading": "sendBroadcastBulk and buildBulkPayload added",572 "body": "Nothing loops over recipients any more. The sender works on a whole batch at a time.",573 "stage": {574 "kind": "view",575 "view": "overview"576 },577 "focus": {578 "kind": "selection",579 "lanes": [],580 "nodes": [581 "send-broadcast-bulk",582 "build-bulk-payload",583 "postmark"584 ],585 "edges": [],586 "messages": []587 }588 },589 {590 "id": "suppression-first",591 "heading": "getSuppressedEmails added before the send",592 "body": "It pulls the blocked addresses once, before any batch is built.",593 "stage": {594 "kind": "view",595 "view": "new-batch-path"596 },597 "focus": {598 "kind": "selection",599 "lanes": [],600 "nodes": [601 "get-suppressed-emails",602 "postmark"603 ],604 "edges": [],605 "messages": []606 }607 },608 {609 "id": "old-path-goes-dark",610 "heading": "processBroadcast and sendSingleEmail removed",611 "body": "sendBroadcastBulk does their job for whole batches.",612 "stage": {613 "kind": "view",614 "view": "overview"615 },616 "focus": {617 "kind": "selection",618 "lanes": [],619 "nodes": [620 "process-broadcast",621 "send-single-email"622 ],623 "edges": [],624 "messages": []625 }626 },627 {628 "id": "sequence-start-to-finish",629 "heading": "The send sequence gained 6 new steps",630 "body": "The queue write is the only step that was there before, and it now stamps the batch size.",631 "stage": {632 "kind": "flow",633 "flow": "send-pipeline"634 },635 "focus": {636 "kind": "all"637 }638 },639 {640 "id": "four-batch-calls",641 "heading": "Postmark now gets 500 emails per call",642 "body": "One call per batch, and Postmark answers with a result for each message.",643 "stage": {644 "kind": "flow",645 "flow": "send-pipeline"646 },647 "focus": {648 "kind": "selection",649 "lanes": [],650 "nodes": [],651 "edges": [],652 "messages": [653 "batch-post",654 "batch-results"655 ]656 }657 },658 {659 "id": "blast-radius",660 "heading": "4 parts added, 2 removed, across 3 lanes",661 "body": "A 2,000-person broadcast used to make 2,000 calls to Postmark. It now makes 4.",662 "stage": {663 "kind": "view",664 "view": "overview"665 },666 "focus": {667 "kind": "all"668 }669 }670 ]671 },672 "layout": {673 "direction": "right",674 "laneOrder": [675 "web",676 "functions",677 "external"678 ],679 "rank": {680 "queue-route": 0,681 "send-broadcast-bulk": 1,682 "postmark": 2683 }684 }685}686 Referenced from SKILL.md
SKILL.mdView in source ↗
Source excerpt starting at line 38.SKILL.mdView in source ↗382. **Write the document** to `.pr-lens/graph.json`, following `references/graph-document.md`. `references/example.graph.json` is a valid reference with three lanes, all four delta states, a hero edge, a seven-step flow, a nested drill-down tree and a six-step walkthrough. Read it before writing your first document — quicker than reading the reference.
Source excerpt starting at line 133.133```bash134cp references/example.graph.json /tmp/prlens-smoke/ && cd /tmp/prlens-smoke135npx -y @coldtea/pr-lens-cli@latest validate example.graph.json