<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pragnesh Shah</title>
    <description>The latest articles on DEV Community by Pragnesh Shah (@pragmatic-builder).</description>
    <link>https://dev.to/pragmatic-builder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962178%2F7c7ac154-c537-4cb2-bf5e-c3577588a44f.jpeg</url>
      <title>DEV Community: Pragnesh Shah</title>
      <link>https://dev.to/pragmatic-builder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pragmatic-builder"/>
    <language>en</language>
    <item>
      <title>The AI Question That Silences Every Leadership Meeting</title>
      <dc:creator>Pragnesh Shah</dc:creator>
      <pubDate>Tue, 16 Jun 2026 07:40:00 +0000</pubDate>
      <link>https://dev.to/pragmatic-builder/the-ai-question-that-silences-every-leadership-meeting-4j28</link>
      <guid>https://dev.to/pragmatic-builder/the-ai-question-that-silences-every-leadership-meeting-4j28</guid>
      <description>&lt;h2&gt;
  
  
  The AI Question That Silences Every Leadership Meeting
&lt;/h2&gt;

&lt;p&gt;One question will tell you whether your AI programme will deliver returns this year. It has nothing to do with which model you're running.&lt;/p&gt;




&lt;p&gt;Every AI strategy meeting follows the same script. Energy is high. Decks are polished. Everyone agrees AI is transformative.&lt;/p&gt;

&lt;p&gt;Then someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What percentage of our AI investment is going into people versus platforms?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The room goes quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ratio worth examining
&lt;/h2&gt;

&lt;p&gt;When less than 70% goes toward people — training, workflow redesign, change management, giving teams space to work differently — it raises a question worth sitting with: are we solving the right problem?&lt;/p&gt;

&lt;p&gt;Most organisations I've seen succeed get this right. The rest get it backwards. They spend 80% on platforms, models, and infrastructure. Then they wonder why adoption is low, value is invisible, and the board keeps asking "where's the ROI?"&lt;/p&gt;

&lt;p&gt;The technology was never the bottleneck. Readiness was.&lt;/p&gt;

&lt;h2&gt;
  
  
  You're spreading thin and calling it strategy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwm2zj8ruwaory7tdwsg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwm2zj8ruwaory7tdwsg.webp" alt="You're Spreading Thin and Calling It Strategy" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's another pattern worth naming: AI deployed thinly across every function. A chatbot here. A summarisation tool there. A coding assistant in engineering.&lt;/p&gt;

&lt;p&gt;Each produces a modest gain. None produces transformation.&lt;/p&gt;

&lt;p&gt;The freed-up capacity goes unmonetised because nobody designed what happens next with the time saved.&lt;/p&gt;

&lt;p&gt;The teams I've seen ship actual AI value all did the same counterintuitive thing: they picked three domains or fewer, went deep, and refused to expand until the economics were proven. Everyone else is running twenty pilots and measuring nothing.&lt;/p&gt;

&lt;p&gt;What does this look like in practice?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A financial services firm concentrating AI on credit decisioning, fraud detection, and client onboarding — three domains, measurable results in six months.&lt;/li&gt;
&lt;li&gt;A manufacturer applying AI to demand forecasting, quality inspection, and supply chain optimisation — repeatable capability before expansion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle: depth before breadth. Build the muscle in one area, prove it pays, then replicate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you had to pick only three areas where AI must deliver measurable revenue or cost reduction this year — which three would survive the cut?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the returns actually are
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwwteruyem9uahqupgb3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwwteruyem9uahqupgb3.webp" alt="Where the Returns Actually Are" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The productivity gains are real. But they follow a specific pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers:&lt;/strong&gt; Claude Code, Kiro, OpenAI Codex — these aren't incrementally faster. They're structurally different. What once required an eight-person squad now collapses to a product owner and a full-stack engineer working alongside AI. I've seen teams report 3-5x throughput on well-scoped tasks. Not on everything. On &lt;em&gt;well-scoped&lt;/em&gt; tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For decision-makers:&lt;/strong&gt; Strategy teams synthesise market signals, model scenarios, and pressure-test assumptions in hours rather than weeks. The value comes not from replacing judgement but from compressing the time to exercise it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For operations:&lt;/strong&gt; Document processing, compliance checking, scheduling, reporting — redesigned end-to-end rather than patched with point solutions. Admin teams that spent 60% of their time on manual processes now spend that time on exception handling and relationship management.&lt;/p&gt;

&lt;p&gt;The common thread? None of these gains came from choosing a better model. They came from redesigning how people work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ceiling Isn't Technical
&lt;/h2&gt;

&lt;p&gt;Put simply: the ceiling on your AI impact isn't the technology. It's your organisation's readiness to absorb and scale it.&lt;/p&gt;

&lt;p&gt;The organisations capturing value aren't those with the fanciest tools. They're the ones with an execution engine — the combination of talent, operating model, and cultural readiness that allows them to repeatedly turn AI capability into outcomes that show up in the numbers.&lt;/p&gt;

&lt;p&gt;Boards are no longer asking "Are you doing AI?" They're asking "Where is it appearing in the financials?"&lt;/p&gt;

&lt;p&gt;If your answer is still a slide deck — the window is narrowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question
&lt;/h2&gt;

&lt;p&gt;Next time you're debating which AI platform to adopt or which model to fine-tune, try this instead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What percentage of our AI investment is going into people versus platforms?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it's below 70% on people — you've found your problem.&lt;/p&gt;

&lt;p&gt;What's your ratio? I'd genuinely like to know.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;#ai #discuss #career #programming&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Used DeepSeek R1 + Open Data to Challenge AI ROI</title>
      <dc:creator>Pragnesh Shah</dc:creator>
      <pubDate>Thu, 04 Jun 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/pragmatic-builder/i-used-deepseek-r1-to-audit-ai-roi-claims-the-numbers-dont-say-what-you-think-lia</link>
      <guid>https://dev.to/pragmatic-builder/i-used-deepseek-r1-to-audit-ai-roi-claims-the-numbers-dont-say-what-you-think-lia</guid>
      <description>&lt;p&gt;Three conversations are happening in organisations right now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We deployed AI right before the rebound — the numbers recovered immediately."&lt;/p&gt;

&lt;p&gt;"Our AI rollout worked. Numbers are up, team is faster."&lt;/p&gt;

&lt;p&gt;"We adopted AI because everyone else did. Eighteen months later, it hasn't moved the bottom line."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the uncomfortable question that runs through all three: would those results have happened anyway?&lt;/p&gt;

&lt;p&gt;The concept that explains why that question is so hard to answer is 150 years old — and it has never been more relevant than it is right now. It is called regression to the mean. Let me show you how it applies across two lenses of real data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Regression to the mean explains why AI investments appear to work — even when they haven't moved anything. SEC data shows 0% natural recovery in 27 trough companies. Stack Overflow and METR data show a 39-point gap between perceived and measured productivity. Three questions to ask before your next board deck are at the bottom.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Regression to the Mean: The Concept Nobody Applies to AI&lt;/li&gt;
&lt;li&gt;Lens A — SEC Revenue Data: The Baseline You Never Set&lt;/li&gt;
&lt;li&gt;Lens B — SO 2024 &amp;amp; METR 2025: The 39-Point Perception Gap&lt;/li&gt;
&lt;li&gt;Solution Design: DeepSeek R1 on AWS Bedrock&lt;/li&gt;
&lt;li&gt;The Three Questions Worth Asking&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Regression to the Mean: The Concept Nobody Applies to AI
&lt;/h2&gt;

&lt;p&gt;Regression to the mean (RTM) describes a simple statistical reality: extreme outcomes drift back toward the average on their own. Not because anything changed — because extreme performance contains more noise than signal, and noise does not persist.&lt;/p&gt;

&lt;p&gt;Let me explain with an example. Take every footballer rated in the &lt;a href="https://www.kaggle.com/datasets/stefanoleone992/fifa-23-complete-player-dataset" rel="noopener noreferrer"&gt;top 10% one season&lt;/a&gt;. The following season, most score lower — not because they declined, but because extreme performance is partly skill and partly circumstance, and circumstance does not persist. The lowest-rated players improve for the same reason. Neither group changed fundamentally. The distribution pulled them back toward the centre.&lt;/p&gt;

&lt;p&gt;Once seen in sport, it is impossible to unsee in a quarterly revenue report. When a metric recovers after an AI deployment, the question is never "would this have happened anyway?" It should be the first question. In AI investment, RTM is the most systematically ignored concept in the room. The AI gets the credit. The measurement goes unquestioned. The baseline is never set.&lt;/p&gt;

&lt;p&gt;RTM takes two forms in AI measurement — one statistical, one cognitive. Both produce the same wrong conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lens A — SEC Revenue Data: The Baseline You Never Set
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Data: &lt;a href="https://www.sec.gov/data-research/sec-markets-data/financial-statement-data-sets" rel="noopener noreferrer"&gt;SEC EDGAR Financial Statement Data Sets 2024 Q4&lt;/a&gt; · SIC 7372 · &lt;a href="//./regre-mean/data/download_commands.txt"&gt;Download datasets&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Software companies that hit a revenue trough recovered — with and without an AI initiative. Among the companies that did invest in AI, some genuinely outperformed. Others recovered at the same rate as those that did nothing. The aggregate signal does not separate them cleanly.&lt;/p&gt;

&lt;p&gt;The FOMO dynamic made this worse. Many organisations did not invest because of operational need. They invested because a competitor mentioned AI in an earnings call. Because the strategy deck required it. Because 'AI first' became the default answer before the question was asked.&lt;/p&gt;

&lt;p&gt;That is a fundamentally different trigger — and it produces a different measurement problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the decision to invest is caused by an extreme trough, statistical reversion is already loaded. The AI initiative and the natural recovery happen simultaneously. Attributing the recovery to the initiative is not dishonest. It is just unmeasured.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The companies that cannot answer 'what would have happened without it?' have not measured their AI. They have measured their quarter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59gqm2nyesk655s6n3e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59gqm2nyesk655s6n3e6.png" alt="Revenue before and after — top 20 most volatile software companies" width="800" height="599"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Top 20 most volatile companies — Q1 vs Q2 revenue (USD M, log scale). Green: recovered. Red: declined. SEC EDGAR, SIC 7372, Q3 2023 – Q4 2024.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What DeepSeek R1 found — Lens A
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F759gih30qudmku95s0es.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F759gih30qudmku95s0es.png" alt="Scatter plot showing 0% revenue recovery among 27 large-drop software companies versus RTM expectation" width="800" height="425"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;0% recovery in 27 large-drop companies contradicts RTM expectations. Mean QoQ (+131.1%) is 34× the median (+3.8%) — a handful of outliers distorting the aggregate signal.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical finding&lt;/strong&gt; &lt;em&gt;(DeepSeek R1)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While 55% of companies showed sequential revenue growth, the complete absence of recovery among 27 largest decliners contradicts classical RTM patterns. Extreme positive skew (median 3.8% vs mean 131.1%) suggests concentrated outliers rather than systemic improvement. This distribution questions blanket AI impact claims — most recoveries cluster in already-stable performers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Executive summary&lt;/strong&gt; &lt;em&gt;(DeepSeek R1)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not one company with severe revenue drops recovered naturally. The staggering 131% average growth figure hides that 3/4 of companies saw less than 4% improvement — the true drivers were pre-existing strong performers, not AI interventions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;So what?&lt;/strong&gt; Before the next board deck: what was the 12-month rolling average before the trough that triggered this investment? That is your baseline. The quarter you invested from is not. Demand counterfactual modelling before renewing any AI contract.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lens B — SO 2024 &amp;amp; METR 2025: The 39-Point Perception Gap
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Data: &lt;a href="https://survey.stackoverflow.co/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey 2024&lt;/a&gt; (n=33,239) · &lt;a href="https://metr.org" rel="noopener noreferrer"&gt;METR 2025 controlled study&lt;/a&gt; · &lt;a href="//./regre-mean/data/download_commands.txt"&gt;Download datasets&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Developers using AI tools took &lt;strong&gt;19% longer&lt;/strong&gt; on real tasks whilst believing they were &lt;strong&gt;20% faster&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a 39-percentage-point gap between perception and measured reality. It holds across every experience level — junior developers, senior developers, architects. The number does not move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzirrkg5pu0hdgvwg0h10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzirrkg5pu0hdgvwg0h10.png" alt="Bar chart of AI sentiment by developer experience level from Stack Overflow 2024 survey" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;AI sentiment (normalised) by experience — SO 2024, n=33,239. Orange dashed: +20% perceived (METR 2025). Red dotted: −19% measured (METR 2025).&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why this is not classical RTM — and why that matters more
&lt;/h3&gt;

&lt;p&gt;Classical RTM requires the same subjects measured twice. The SO 2024 survey and METR 2025 are different populations, measured once each. Strict RTM does not apply.&lt;/p&gt;

&lt;p&gt;What this is instead is &lt;strong&gt;RTM's cognitive cousin&lt;/strong&gt; — and it produces the same wrong conclusion by a different route.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Regression to the mean describes what happens to numbers measured from an extreme. The 39-point perception gap describes what happens to judgement formed at an extreme moment — adoption. Developers anchor their baseline at maximum enthusiasm. Every measurement taken from that anchor inflates the perceived gain. Different mechanism, identical error.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  What DeepSeek R1 found — Lens B
&lt;/h3&gt;

&lt;p&gt;R1 received adoption rates, trust scores, and sentiment distributions. The chart below is what it returned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vi9oncxd503vfmjjpkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vi9oncxd503vfmjjpkw.png" alt="RTM analysis chart showing 39-point gap between perceived and measured AI productivity" width="800" height="444"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Mechanism: cognitive attribution bias. Perceived productivity gain +20% vs measured change −19%. The shaded gap is the 39-point perception-reality chasm.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical finding&lt;/strong&gt; &lt;em&gt;(DeepSeek R1)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The 39-point METR gap aligns with predictable variance patterns: senior developers show both highest volatility (σ=0.91) and largest sentiment/reality disconnect. While AI adoption correlates with a 0.49-point sentiment lift, the −19 actual METR reveals systemic attribution errors. Experience cohorts' inverse trust/performance relationship suggests senior teams over-attribute natural workflow variance to tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Executive summary&lt;/strong&gt; &lt;em&gt;(DeepSeek R1)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Engineering leaders report 20% productivity gains from AI tools, but objective metrics show 19% decline — a 39-point reality gap. This mirrors the 13.4% sentiment drop from 2023's AI hype cycle, showing how teams misattribute normal performance variance to technology interventions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;So what?&lt;/strong&gt; The 39-point gap is the number to take into your next vendor conversation. Ask for task-completion data from a controlled study — not survey sentiment. If they cannot produce it, you are buying confidence, not capability.&lt;/p&gt;
&lt;h3&gt;
  
  
  What this means for your measurement
&lt;/h3&gt;

&lt;p&gt;If your organisation is in boardroom conversation two — &lt;em&gt;"our AI rollout worked, the numbers are up"&lt;/em&gt; — this lens applies directly. The question is not whether the numbers went up. They probably did. The question is whether the team's enthusiasm at the moment of adoption has been baked into the baseline you are measuring from.&lt;/p&gt;

&lt;p&gt;Most AI productivity measurements are taken by people who chose to believe in the tool, at the moment they chose to believe in it. That is the least reliable baseline possible. The 39-point gap is what happens when you measure from there and call it evidence.&lt;/p&gt;

&lt;p&gt;The business reality in 2025: AI is now a board mandate in most organisations, not an operational choice. That means adoption is happening regardless of individual conviction — which makes the attribution problem worse, not better. FOMO-driven adoption inflates the enthusiast pool, inflates the baseline sentiment, and inflates the reported gains. The underlying performance data, as METR measured it, points the other way.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution Design: DeepSeek R1 on AWS Bedrock
&lt;/h2&gt;

&lt;p&gt;Same pipeline for both lenses: Python computes stats → DeepSeek R1 reasons over them → structured JSON drives charts and written findings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcu06gc8h2x049cdhm5jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcu06gc8h2x049cdhm5jx.png" alt="Pipeline diagram showing data flow from public datasets through Python stats to DeepSeek R1 on AWS Bedrock producing JSON, charts, and narration" width="798" height="140"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  One prompt, two lenses
&lt;/h3&gt;

&lt;p&gt;One DeepSeek R1 prompt handles both datasets. Only the context block changes per lens — topic, audience, purpose. The model receives the full stats dictionary and returns structured JSON. Here is the actual Lens A output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rtm_present"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mechanism"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Extreme Value Distortion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key_finding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0% recovery in large-drop companies contradicts RTM despite 55.1% overall recovery."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surprise"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mean QoQ of 131.1% vs median 3.8% reveals extreme outlier-driven skew."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chart_points"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Median QoQ growth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"annotation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Typical company performance"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Large-drop recovery rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"annotation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Zero bounce-back despite RTM expectations"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mean QoQ growth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;131.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"annotation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Skewed by extreme outliers"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gap_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Median-Mean Disparity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gap_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;127.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"highlight_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"29 companies drove 98% of growth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"narration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"While 55% of companies showed sequential revenue growth, the complete absence of recovery among 27 largest decliners contradicts classical RTM. Extreme positive skew (median 3.8% vs mean 131.1%) suggests concentrated outliers rather than systemic improvement."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executive_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not one company with severe revenue drops recovered naturally. The 131% average hides that 3/4 of companies saw less than 4% improvement — true drivers were pre-existing strong performers."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chart axes, annotations, and shaded gap regions are all derived from this output. Change the dataset, the chart changes. Change the audience context, the written outputs change.&lt;/p&gt;

&lt;h3&gt;
  
  
  R1's thinking (excerpt — Lens A)
&lt;/h3&gt;

&lt;p&gt;Before producing JSON, R1 reasons through the data. Here is a direct excerpt from its chain-of-thought:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The overall recovery rate is 55.1% — slightly above half. RTM would predict that
companies performing worse than average would improve. That's consistent with
the 55% figure. But the mean QoQ is 131.1%, way higher than the median of 3.8%.
A highly skewed distribution. A few companies had massive increases pulling the
mean up. That skewness might indicate outliers influencing the average.

The large drop recovery rate being 0% complicates things. If those with the
biggest drops didn't recover, maybe RTM isn't the main factor for them. Their
issues might be structural — not random fluctuations that naturally correct.

RTM might be present in the general recovery (55%), but the 0% recovery in
large drops is unexpected. The mechanism: cognitive bias where companies
attribute the high mean (from a few strong recoveries) to AI, ignoring the
complete lack of recovery in the worst performers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three numbers fed the model: QoQ recovery rate (&lt;code&gt;55.1%&lt;/code&gt;), large-drop recovery rate (&lt;code&gt;0.0%&lt;/code&gt;), mean-median gap (&lt;code&gt;131.1 − 3.8 = 127.3 points&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  What failed first
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Mistral 7B hallucinated the acronyms it was told not to expand.&lt;/strong&gt; Three runs, three different inventions for "RTM": Root Mean Square Error, Return on Technology Investment, Relative Technological Maturity. MATH-500 score: ~42% vs DeepSeek R1's 97.3%. At that capability gap, token priors beat instructions. Model replaced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-prescribed prompts killed the output.&lt;/strong&gt; Ten-point briefs — paragraph structure, tone, anchoring numbers — produced technically correct, completely bland text. Stripping to context + data + one instruction fixed it. The model needs room to reason, not a script to follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A normalisation rule in the prompt broke JSON output.&lt;/strong&gt; "Multiply raw scores by 50 if on a −2 to +2 scale" caused R1 to burn its entire thinking budget on a scale conflict. Empty JSON returned. Fix: handle normalisation in &lt;code&gt;plot_rtm()&lt;/code&gt;, not in natural language. Scale conversion belongs in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numeric outputs were stable across runs; interpretive labels were not (even at &lt;code&gt;temperature: 0&lt;/code&gt;).&lt;/strong&gt; The same data produced consistent numbers every time. The mechanism label varied. Worth keeping in mind when using reasoning models in a pipeline: anchor on the numbers, treat the prose framing as context.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Questions Worth Asking
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What was my baseline before the trough that triggered this investment?&lt;/strong&gt;&lt;br&gt;
Not the crisis quarter. The 12-month rolling average before it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What would have happened without the intervention?&lt;/strong&gt;&lt;br&gt;
If you cannot model the counterfactual, you have not measured the AI. You have measured the quarter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Have I measured across at least three consecutive cycles?&lt;/strong&gt;&lt;br&gt;
One data point is noise. Two is coincidence. Three is a pattern worth acting on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your experience measuring AI ROI in your organisation? Has your team found a reliable counterfactual approach? Drop a comment — I'd genuinely like to know what's working.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Built a Document Triage with Telegram, n8n, and AWS Bedrock — 6 Decisions That Shaped a Self-Hosted AI Document Analyst</title>
      <dc:creator>Pragnesh Shah</dc:creator>
      <pubDate>Mon, 01 Jun 2026 16:02:03 +0000</pubDate>
      <link>https://dev.to/pragmatic-builder/i-built-a-document-triage-with-telegram-n8n-and-aws-bedrock-6-decisions-that-shaped-a-3637</link>
      <guid>https://dev.to/pragmatic-builder/i-built-a-document-triage-with-telegram-n8n-and-aws-bedrock-6-decisions-that-shaped-a-3637</guid>
      <description>&lt;p&gt;The volume of documents landing on my mobile outpaces my ability to read them. Research papers, articles, books — shared as PDFs across channels faster than any individual can process. The question isn't 'how do I read more?' — it's 'how do I decide what deserves my attention in 30 seconds, not 30 minutes?'&lt;/p&gt;

&lt;p&gt;So I built this triage system &lt;/p&gt;

&lt;p&gt;Telegram mandates HTTPS for webhooks. I had no domain to attach a certificate to. Here's how a self-signed cert, an Nginx proxy, and a reverse-engineered secret token got a PDF-summarising bot into production on EC2 with AWS Bedrock doing the thinking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: No domain → self-signed cert uploaded to Telegram API → Nginx TLS termination → n8n orchestration → Bedrock summarisation. Zero access keys. Full architecture below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Constraints&lt;/li&gt;
&lt;li&gt;Architecture: Telegram to Bedrock on EC2&lt;/li&gt;
&lt;li&gt;Key Design Decisions&lt;/li&gt;
&lt;li&gt;What Failed First&lt;/li&gt;
&lt;li&gt;The Implementation That Shipped&lt;/li&gt;
&lt;li&gt;What I'd Do Differently&lt;/li&gt;
&lt;li&gt;Takeaways for Architects&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Constraints
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;None available — no Route 53 hosted zone, no ACM certificate possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;Mandatory — Telegram rejects webhook registration without TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth model&lt;/td&gt;
&lt;td&gt;No long-lived credentials in config files or environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;Visual workflow preferred — needs to be modifiable without redeployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;AWS-native, no external API keys beyond what IAM provides&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Architecture: Telegram to Bedrock on EC2
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86rqceehzaug22kb10q7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86rqceehzaug22kb10q7.png" alt="Architecture diagram showing Telegram webhook flow through Nginx TLS termination to n8n orchestration on EC2, invoking AWS Bedrock for PDF summarisation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data flow&lt;/strong&gt;: User sends PDF → Telegram delivers webhook POST to EC2:443 → Nginx terminates TLS, proxies to n8n:5678 → n8n downloads file, extracts text, invokes Bedrock → response sent back via Telegram Bot API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;p&gt;Every architecture is a set of trade-offs made explicit. Here are the ones that shaped this system:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Trade-off Accepted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Self-signed cert + Nginx&lt;/strong&gt; (no domain needed)&lt;/td&gt;
&lt;td&gt;Telegram accepts uploaded certs via &lt;code&gt;setWebhook&lt;/code&gt; API; eliminates domain dependency entirely&lt;/td&gt;
&lt;td&gt;Browser shows cert warnings; webhook registration is manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;n8n over custom code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visual workflow with built-in Telegram, PDF extraction, and LLM chain nodes. Hours to build, not weeks.&lt;/td&gt;
&lt;td&gt;Undocumented webhook secret behaviour; version-pinning required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EC2 direct deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud infrastructure with native internet connectivity. Predictable networking for webhook delivery.&lt;/td&gt;
&lt;td&gt;Monthly compute cost; single point of failure without ASG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;IAM role, not access keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero rotation burden, no exposure risk, automatic credential refresh via instance metadata&lt;/td&gt;
&lt;td&gt;None — strictly superior for EC2-hosted workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Manual webhook with computed secret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;n8n can't upload self-signed certs when registering webhooks; manual &lt;code&gt;setWebhook&lt;/code&gt; call bridges the gap&lt;/td&gt;
&lt;td&gt;Must re-run registration script after workflow changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Encryption key in Secrets Manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key loss = total credential loss. Secrets Manager provides audit logging, durability, and prevents accidental exposure in config files&lt;/td&gt;
&lt;td&gt;Extra API call at startup (~100ms latency)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What Failed First
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tunnel services: unreliable for webhook delivery
&lt;/h3&gt;

&lt;p&gt;I tried localhost.run, cloudflared, and ngrok to expose a local n8n instance. All three connected via SSH but HTTP traffic never arrived reliably. Tunnel services that rely on custom subdomains (&lt;code&gt;*.lhr.life&lt;/code&gt;, &lt;code&gt;*.trycloudflare.com&lt;/code&gt;) introduce a dependency outside your control — DNS resolution, uptime, and connection stability are all delegated to a third party. For a webhook endpoint that needs to be reachable 24/7, that's a risk I wasn't willing to accept.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook 403s: proxy trust misconfiguration
&lt;/h3&gt;

&lt;p&gt;After deploying to EC2 with Nginx in front of n8n, every Telegram webhook returned &lt;code&gt;403 Forbidden&lt;/code&gt;. The fix: &lt;code&gt;N8N_PROXY_HOPS=1&lt;/code&gt;. Without it, n8n doesn't trust the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header from Nginx and rejects requests as spoofed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encryption key mismatch: silent data loss
&lt;/h3&gt;

&lt;p&gt;I recreated the Docker container with a different encryption key. n8n showed 'Set up owner account' — all existing workflows and credentials were gone. The encryption key and the Docker volume are a coupled pair. Change one without the other and you lose everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The undocumented secret token
&lt;/h3&gt;

&lt;p&gt;n8n v2.22.5 enforces a secret token on Telegram webhook requests. After extensive testing and reading the source code, I found the formula follows this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;secret_token = {workflowId}_{nodeId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;workflowId&lt;/code&gt; is visible in the browser URL. The &lt;code&gt;nodeId&lt;/code&gt; appears when you click the Telegram Trigger node. This isn't surfaced in the n8n documentation or UI — it required empirical discovery. Without the correct secret, Telegram receives a &lt;code&gt;403&lt;/code&gt; on every delivery and your bot stays silent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Implementation That Shipped
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker: n8n with IAM role access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fetch encryption key from Secrets Manager (never hardcode)&lt;/span&gt;
&lt;span class="nv"&gt;N8N_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; &amp;lt;your-secret-id&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;your-region&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; SecretString &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; n8n &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBHOOK_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;your-ec2-public-ip&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;N8N_EDITOR_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;your-ec2-public-ip&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;N8N_ENCRYPTION_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;N8N_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;GENERIC_TIMEZONE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Europe/London &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;N8N_AWS_SYSTEM_CREDENTIALS_ACCESS_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;N8N_PROXY_HOPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;NODE_TLS_REJECT_UNAUTHORIZED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5678:5678 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; n8n_data:/home/node/.n8n &lt;span class="se"&gt;\&lt;/span&gt;
  docker.n8n.io/n8nio/n8n:2.22.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions embedded in this config:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;N8N_AWS_SYSTEM_CREDENTIALS_ACCESS_ENABLED=true&lt;/code&gt; — uses EC2 IAM role, no access keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N8N_PROXY_HOPS=1&lt;/code&gt; — trusts exactly one proxy layer (Nginx)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt; fetched from Secrets Manager at runtime, never stored on disk&lt;/li&gt;
&lt;li&gt;Version pinned to &lt;code&gt;2.22.5&lt;/code&gt; — webhook secret behaviour changes between versions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Nginx: TLS termination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/etc/ssl/certs/n8n.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/ssl/private/n8n.key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:5678&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_request_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;proxy_request_buffering off&lt;/code&gt; is critical — without it, Nginx buffers the request body and n8n fails to parse multipart uploads (PDFs).&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook registration: the self-signed cert trick
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"url=https://&amp;lt;your-ec2-public-ip&amp;gt;/webhook/&amp;lt;your-webhook-id&amp;gt;/webhook"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"certificate=@/etc/ssl/certs/n8n.pem"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"secret_token=&amp;lt;workflowId&amp;gt;_&amp;lt;nodeId&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.telegram.org/bot&amp;lt;your-bot-token&amp;gt;/setWebhook"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things happen in this single call:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Telegram learns the webhook URL&lt;/li&gt;
&lt;li&gt;The self-signed public cert is uploaded — Telegram will trust it for future deliveries&lt;/li&gt;
&lt;li&gt;The secret token is registered — Telegram includes it as a header, n8n validates it&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Workflow node chain
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4iak9eo3qy3h9f6xttj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4iak9eo3qy3h9f6xttj.png" alt="n8n workflow pipeline showing Telegram Trigger, Get File, Extract PDF, Basic LLM Chain with AWS Bedrock, and Send Message nodes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The LLM prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyse the following document and provide:
1. Title/Subject
2. Key Insights (3-5 bullet points)
3. Summary (2-3 paragraphs)

Document text:
{{ $json.text }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System message: &lt;em&gt;'You are a document analyst. Provide clear summaries. Format for Telegram.'&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Register a domain.&lt;/strong&gt; Self-signed certs work but add operational friction — manual webhook re-registration after cert renewal, browser warnings on the editor. A domain costs as little as $5/year, and with ACM providing free certificates, the entire self-signed complexity disappears. For an MVP this was acceptable; for anything beyond, it's the first thing I'd change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switch to PostgreSQL for n8n's backend.&lt;/strong&gt; n8n officially supports PostgreSQL as its production database (SQLite is the default; MySQL/MariaDB are deprecated). SQLite lives inside the Docker volume — it locks on writes, doesn't support safe hot backups, and is incompatible with n8n's queue mode for horizontal scaling. Amazon RDS for PostgreSQL or Aurora Serverless v2 would give managed backups, point-in-time recovery, and a path to multi-worker deployments without touching the application layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add an Auto Scaling Group with min=1 and a launch template.&lt;/strong&gt; The current architecture is a single EC2 instance — one availability zone, one point of failure. An ASG with the same user-data script gives self-healing (automatic replacement on health check failure) without adding architectural complexity. Combined with a domain and ACM, this moves the system from 'working prototype' to 'production-grade' with minimal additional cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways for Architects
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Self-signed certificates are a valid pattern&lt;/strong&gt; — but only when the webhook consumer explicitly supports certificate upload. Telegram does. Most services (Stripe, GitHub, Slack) do not. Validate this before committing to the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM roles are strictly superior to access keys for EC2 workloads.&lt;/strong&gt; No rotation, no exposure risk, automatic refresh. There's no trade-off here — just use them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin your versions.&lt;/strong&gt; n8n's webhook secret enforcement appeared between versions without a migration path. &lt;code&gt;n8n:latest&lt;/code&gt; is a liability. Pin, test upgrades in staging, keep a rollback plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug webhooks layer by layer.&lt;/strong&gt; Five distinct failure modes: network, TLS, authentication, application logic, response formatting. Each must pass before testing the next. Skipping layers leads to circular troubleshooting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put intelligence in the prompt, not the infrastructure.&lt;/strong&gt; The entire 'AI' part of this system is a 5-line prompt template. The other 95% of effort was infrastructure — certificates, secrets, networking, Docker volumes. The model is a commodity. Getting data to and from it reliably is the craft.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your approach when you need HTTPS webhooks but can't get a domain? I'd be curious whether others have hit the same self-signed cert pattern — or found a better workaround. Drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>aws</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
