<?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: Tetiana Mostova</title>
    <description>The latest articles on DEV Community by Tetiana Mostova (@teti_most).</description>
    <link>https://dev.to/teti_most</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1126677%2F13a7aa1d-6abb-48cb-92f0-790836504f90.PNG</url>
      <title>DEV Community: Tetiana Mostova</title>
      <link>https://dev.to/teti_most</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/teti_most"/>
    <language>en</language>
    <item>
      <title>AWS Strands Replaced 40 Lines of LangGraph With 3. Here’s the Full Story</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Sun, 08 Mar 2026 16:13:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-strands-replaced-60-lines-of-langgraph-with-3-heres-the-full-story-2ok0</link>
      <guid>https://dev.to/aws-builders/aws-strands-replaced-60-lines-of-langgraph-with-3-heres-the-full-story-2ok0</guid>
      <description>&lt;p&gt;At work, we build agents using LangChain and LangGraph. It works, but it takes a while to get anything off the ground. When AWS dropped Strands Agents earlier this year, I was curious — is this actually simpler, or just another framework with a different name?&lt;/p&gt;

&lt;p&gt;Here’s what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Even Is Strands?
&lt;/h2&gt;

&lt;p&gt;Strands Agents is an open source SDK from AWS, released in May 2025. The whole idea is a &lt;strong&gt;model-driven approach&lt;/strong&gt; — instead of you wiring up every step of an agent’s logic, you hand the LLM a prompt and a set of tools, and let it figure out what to do.&lt;/p&gt;

&lt;p&gt;Three things are all you need to build an agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A model (Bedrock, Anthropic, OpenAI, Ollama — your pick)&lt;/li&gt;
&lt;li&gt;A system prompt&lt;/li&gt;
&lt;li&gt;A list of tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. The LLM plans, reasons, calls tools, and loops until the task is done.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By the way — why ‘Strands’? Think of each tool as a single thread. The agent weaves them together dynamically to complete a task. Compare that to LangGraph where you manually braid the threads in a specific order. With Strands, you hand the model a bundle of threads and say ‘figure it out.’ Honestly, a pretty good name for what it does.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code Difference Is Stark
&lt;/h2&gt;

&lt;p&gt;Let’s say you want an agent that can answer a question and use a calculator tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With LangGraph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.messages&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HumanMessage&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_aws&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatBedrock&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Evaluate a math expression&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatBedrock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic.claude-3-sonnet-20240229-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;llm_with_tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind_tools&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm_with_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;tool_call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.messages&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ToolMessage&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ToolMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;tool_call_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])]}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_continue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;

&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_entry_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_conditional_edges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;should_continue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is 1764 square rooted?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Strands:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the square root of 1764?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, that’s really it. Three lines.&lt;/p&gt;

&lt;p&gt;The LangGraph version above is ~40 lines for the same task — and that's still the happy path, no error handling yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  So Should You Just Switch to Strands?
&lt;/h2&gt;

&lt;p&gt;Not necessarily. Here’s the honest breakdown:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strands is better when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re prototyping fast and don’t need tight control over every step&lt;/li&gt;
&lt;li&gt;You trust the LLM to reason well for your use case&lt;/li&gt;
&lt;li&gt;You’re already on AWS and want native Bedrock + Lambda + ECS integration&lt;/li&gt;
&lt;li&gt;Your agent is relatively self-contained (one agent, many tools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;LangGraph is still better when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need precise, predictable control over the flow (e.g. financial workflows, compliance-heavy systems)&lt;/li&gt;
&lt;li&gt;You have complex branching logic that you can’t leave up to the model&lt;/li&gt;
&lt;li&gt;You need parallel sub-agent execution with strict state isolation&lt;/li&gt;
&lt;li&gt;Your team already has a lot of LangGraph investment and tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key difference is the &lt;strong&gt;philosophy&lt;/strong&gt;. LangGraph says: you design the graph, the model executes it. Strands says: you give the model tools and let it figure out the graph itself.&lt;/p&gt;

&lt;p&gt;In practice, Strands works great for tasks where the LLM is good at reasoning through what to do next. It starts to feel shaky when you have very specific business logic that must run in a precise order — that’s where explicit graph control still wins.&lt;/p&gt;




&lt;h2&gt;
  
  
  AWS Integration Is a Real Advantage
&lt;/h2&gt;

&lt;p&gt;If you’re already in the AWS ecosystem, Strands has a genuine edge. It deploys natively to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; (for serverless agent invocations)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ECS/Fargate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock AgentCore&lt;/strong&gt; — a managed runtime that handles identity (IAM, Cognito), memory, long-running tasks (up to 8 hours), and observability out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding observability is also almost zero effort. Strands has built-in OpenTelemetry support, so traces go straight to CloudWatch, X-Ray, or any OTLP-compatible tool.&lt;/p&gt;

&lt;p&gt;With LangChain, you’re usually reaching for LangSmith or wiring up your own tracing. It works, but it’s extra setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Error Handling?
&lt;/h2&gt;

&lt;p&gt;This one surprised me. In LangGraph you explicitly wire up what happens when something fails — fallback edges, retry nodes, conditional logic. It’s more code but you’re in full control.&lt;br&gt;
Strands takes a different approach: when something goes wrong, the model reasons about alternatives rather than following a predetermined error path. For most cases that actually works fine.&lt;br&gt;
For production tools that call external APIs, you can still add a resilience decorator with retries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RetryConfig&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;RetryConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backoff_multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_external_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Call an external API&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# your API call here
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What About Multi-Agent Setups?
&lt;/h2&gt;

&lt;p&gt;Strands has built-in patterns for this — Graph, Swarm, and Workflow modes. You can also expose one Strands agent as a tool for another agent, which makes composing multi-agent systems pretty clean.&lt;/p&gt;

&lt;p&gt;For very large multi-agent architectures (many specialized sub-agents with their own isolated contexts), AWS actually recommends &lt;strong&gt;Agent Squad&lt;/strong&gt; over Strands. Agent Squad is a separate library focused purely on routing and orchestration across many agents. Strands + Agent Squad can be used together.&lt;/p&gt;

&lt;p&gt;LangGraph is still competitive here for complex multi-agent graphs, especially when you need fine-grained state sharing between nodes.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Take
&lt;/h2&gt;

&lt;p&gt;I came in skeptical. Another SDK, another abstraction, does the world really need this?&lt;/p&gt;

&lt;p&gt;But Strands is genuinely simpler for the 80% case. If your agent needs to answer questions, call APIs, retrieve docs, or do multi-step reasoning — just use Strands. You’ll be done in an hour instead of a day.&lt;/p&gt;

&lt;p&gt;If you’re building something where the sequence of operations really matters and you can’t leave decisions to the model, keep LangGraph. It’s not going anywhere, and the explicit graph control is valuable.&lt;/p&gt;

&lt;p&gt;For teams already on AWS, I’d start new agent projects with Strands and reach for LangGraph only when I hit a wall.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;strands-agents strands-agents-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get current weather for a city&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# your actual API call here
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;It&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s sunny in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, 22°C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a helpful travel assistant.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather like in Lisbon?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you have AWS credentials configured and Bedrock model access enabled (Claude Sonnet in &lt;code&gt;us-west-2&lt;/code&gt; by default).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Using LangGraph at work and tried Strands? I’d love to hear how it compared for your use case — drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>aws</category>
      <category>llm</category>
    </item>
    <item>
      <title>Building an Intelligent Document Processing Pipeline with AWS: A Journey from Idea to Production</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Wed, 24 Dec 2025 14:45:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-an-intelligent-document-processing-pipeline-with-aws-a-journey-from-idea-to-production-c6n</link>
      <guid>https://dev.to/aws-builders/building-an-intelligent-document-processing-pipeline-with-aws-a-journey-from-idea-to-production-c6n</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR - Want to Skip the Story?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fork the repo, deploy in 10 minutes, start processing documents:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Tetianamost/aws-intelligent-document-processing.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aws-intelligent-document-processing
sam build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is production-ready. Just add your AWS credentials and email. Keep reading if you want to know how it works and what I learned building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This (And Why You Might Need It Too)
&lt;/h2&gt;

&lt;p&gt;Picture this: You're drowning in invoices, receipts, tax forms, and contracts. Your team is manually typing data from PDFs into spreadsheets. Hours wasted. Errors everywhere. Sound familiar?&lt;/p&gt;

&lt;p&gt;I built an automated document processing system using AWS services that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extracts text, tables, and form data&lt;/strong&gt; from any document automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processes documents in real-time&lt;/strong&gt; as soon as they're uploaded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stores structured data&lt;/strong&gt; ready for analysis or integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sends notifications&lt;/strong&gt; when processing completes (or fails)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales effortlessly&lt;/strong&gt; from 10 to 10,000 documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? Once deployed, it costs &lt;strong&gt;pennies per document&lt;/strong&gt; and requires zero maintenance. And you don't have to build it from scratch - just fork, deploy, and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Simple but Powerful
&lt;/h2&gt;

&lt;p&gt;Here's what I built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Upload (S3) 
    ↓
Automatic Trigger (S3 Event)
    ↓
AI Processing (Lambda + Textract)
    ↓
Structured Storage (DynamoDB)
    ↓
Notification (SNS Email)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The magic happens in seconds:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drop a document in S3&lt;/li&gt;
&lt;li&gt;Lambda wakes up automatically&lt;/li&gt;
&lt;li&gt;Textract extracts everything (text, tables, forms)&lt;/li&gt;
&lt;li&gt;Data lands in DynamoDB, perfectly structured&lt;/li&gt;
&lt;li&gt;You get an email notification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No servers to manage. No infrastructure to maintain. Just pure serverless goodness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes This Cool?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;It Actually Understands Your Documents&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This isn't just OCR. Amazon Textract uses machine learning to understand document structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tables&lt;/strong&gt;: Extracts rows and columns with relationships intact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forms&lt;/strong&gt;: Identifies key-value pairs (Invoice #: 12345, Date: Dec 23, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt;: Gets every word with confidence scores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tested it with a business invoice containing 4 line items, calculations, and company details. &lt;strong&gt;Textract extracted 155 text blocks&lt;/strong&gt; and perfectly reconstructed the entire table structure. Here's what it found:&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;"fields"&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;"Invoice #"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-2024-001234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"December 23, 2024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$30,922.50"&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;"tables"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"data"&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="s2"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quantity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Total"&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="s2"&gt;"AWS Cloud Setup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$5,000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$5,000"&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="s2"&gt;"DevOps Consulting (80 hrs)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$175/hr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$14,000"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&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="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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Zero Infrastructure Management&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Remember the days of provisioning servers, configuring load balancers, and setting up auto-scaling? Yeah, me neither. With this serverless architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; handles compute (only runs when needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3&lt;/strong&gt; triggers events automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; scales infinitely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; monitors everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I literally deployed this, uploaded a document, and walked away. It just works.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Cost-Effective at Any Scale&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let's talk money:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Textract&lt;/strong&gt;: $1.50 per 1,000 pages (first 1M pages/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt;: First 1M requests free, then $0.20 per 1M&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3&lt;/strong&gt;: $0.023 per GB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: First 25 GB free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; Processing 1,000 invoices per month costs about &lt;strong&gt;$1.50&lt;/strong&gt;. That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Production-Ready Error Handling&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Things fail. Networks hiccup. Documents are corrupted. I built in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dead Letter Queue&lt;/strong&gt; for failed processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Alarms&lt;/strong&gt; for error monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS notifications&lt;/strong&gt; for both success and failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic retries&lt;/strong&gt; with exponential backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status tracking&lt;/strong&gt; in DynamoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When something breaks, you know immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges (And How I Solved Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge #1: Circular Dependencies in CloudFormation
&lt;/h3&gt;

&lt;p&gt;My first SAM deployment failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Circular dependency between resources: [DocumentBucket, DocumentProcessorFunction, DocumentProcessorFunctionPermission]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; S3 bucket needed Lambda permission, Lambda needed the bucket name, and the permission needed both. Classic chicken-and-egg.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy infrastructure without S3 event notifications&lt;/li&gt;
&lt;li&gt;Add S3 notifications separately using AWS CLI&lt;/li&gt;
&lt;li&gt;Keep Lambda permission with explicit DependsOn&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lesson learned: Sometimes you need to break CloudFormation into multiple steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #2: PDF Format Compatibility Hell
&lt;/h3&gt;

&lt;p&gt;This was the big one. I generated beautiful PDFs using Python's reportlab library. They looked perfect. &lt;strong&gt;Textract rejected every single one.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UnsupportedDocumentException: Request has unsupported document format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even the official IRS Form 1040 PDF failed!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Investigation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Textract supports PDFs (per AWS docs) ✅&lt;/li&gt;
&lt;li&gt;My PDFs were valid (opened fine in Preview) ✅&lt;/li&gt;
&lt;li&gt;File size was under 5MB ✅&lt;/li&gt;
&lt;li&gt;But still... rejected ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Breakthrough:&lt;/strong&gt; Not all PDFs are created equal. Textract is picky about internal PDF structure. PDFs generated by reportlab, fpdf, and even some government forms use formats Textract doesn't support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt; Convert PDFs to PNG/JPG images first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using poppler's pdftoppm&lt;/span&gt;
pdftoppm &lt;span class="nt"&gt;-png&lt;/span&gt; &lt;span class="nt"&gt;-singlefile&lt;/span&gt; document.pdf output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IRS Form 1040 as PDF: ❌ Failed&lt;/li&gt;
&lt;li&gt;Same form as PNG: ✅ &lt;strong&gt;1,412 blocks extracted&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reportlab invoice as PDF: ❌ Failed
&lt;/li&gt;
&lt;li&gt;Same invoice as PNG: ✅ &lt;strong&gt;155 blocks extracted&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; If you're programmatically generating documents for Textract, create them as PNG/JPG from the start. Save yourself the headache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge #3: IAM Permissions Maze
&lt;/h3&gt;

&lt;p&gt;SAM deployment needs a surprising number of AWS permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFormation (to create stacks)&lt;/li&gt;
&lt;li&gt;Lambda (to deploy functions)&lt;/li&gt;
&lt;li&gt;IAM (to create execution roles)&lt;/li&gt;
&lt;li&gt;S3 (to store artifacts and documents)&lt;/li&gt;
&lt;li&gt;DynamoDB (to create tables)&lt;/li&gt;
&lt;li&gt;SNS (to create topics)&lt;/li&gt;
&lt;li&gt;CloudWatch (to create log groups and alarms)&lt;/li&gt;
&lt;li&gt;X-Ray (for tracing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Practice:&lt;/strong&gt; Create an IAM user group with all required managed policies, then add your deployment user to it. This makes permission management cleaner and reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Results: What It Actually Extracted
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test 1: Business Invoice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt; PNG image with company header, bill-to info, 4 line items, and calculations&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extracted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 key-value pairs (Invoice #, dates, addresses)&lt;/li&gt;
&lt;li&gt;Complete 8x4 table with all line items&lt;/li&gt;
&lt;li&gt;Subtotal, tax, and total calculations&lt;/li&gt;
&lt;li&gt;155 total text blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Processing time:&lt;/strong&gt; ~2 seconds&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: IRS Form 1040 (Tax Form)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt; Official IRS form converted to PNG&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extracted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,412 text blocks&lt;/li&gt;
&lt;li&gt;All form field labels&lt;/li&gt;
&lt;li&gt;Form structure and layout&lt;/li&gt;
&lt;li&gt;Every piece of text on the form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Processing time:&lt;/strong&gt; ~3 seconds&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: From Zero to Production in 10 Minutes
&lt;/h2&gt;

&lt;p&gt;Here's what it actually took:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Build the application&lt;/span&gt;
sam build

&lt;span class="c"&gt;# 2. Deploy to AWS&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--stack-name&lt;/span&gt; doc-processing-dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_IAM &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;NotificationEmail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your@email.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resolve-s3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-confirm-changeset&lt;/span&gt;

&lt;span class="c"&gt;# 3. Configure S3 event notifications&lt;/span&gt;
aws s3api put-bucket-notification-configuration &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; your-bucket-name &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--notification-configuration&lt;/span&gt; file://s3-notification-config.json

&lt;span class="c"&gt;# Done!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudFormation created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 S3 bucket&lt;/li&gt;
&lt;li&gt;1 Lambda function&lt;/li&gt;
&lt;li&gt;1 DynamoDB table&lt;/li&gt;
&lt;li&gt;1 SNS topic with email subscription&lt;/li&gt;
&lt;li&gt;2 CloudWatch alarms&lt;/li&gt;
&lt;li&gt;1 SQS dead letter queue&lt;/li&gt;
&lt;li&gt;All IAM roles and permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total deployment time:&lt;/strong&gt; 3 minutes&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with PNG/JPG from day one&lt;/strong&gt; - Would have saved hours of PDF debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add async processing earlier&lt;/strong&gt; - For multi-page documents, async Textract jobs are better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include cost alerts&lt;/strong&gt; - Set up billing alarms before deploying&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add API Gateway&lt;/strong&gt; - Would make it easy to trigger processing via HTTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement document versioning&lt;/strong&gt; - Track changes to processed documents&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Building this taught me that serverless isn't just a buzzword - it's genuinely transformative for document processing workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic document processing in seconds&lt;/li&gt;
&lt;li&gt;AI-powered data extraction (not just OCR)&lt;/li&gt;
&lt;li&gt;Zero server management&lt;/li&gt;
&lt;li&gt;Pay-per-use pricing&lt;/li&gt;
&lt;li&gt;Production-ready error handling&lt;/li&gt;
&lt;li&gt;Infinite scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What it costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~$1.50 per 1,000 documents&lt;/li&gt;
&lt;li&gt;A few hours to set up&lt;/li&gt;
&lt;li&gt;Zero ongoing maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you save:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hundreds of hours of manual data entry&lt;/li&gt;
&lt;li&gt;Countless transcription errors&lt;/li&gt;
&lt;li&gt;Server infrastructure costs&lt;/li&gt;
&lt;li&gt;DevOps overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ready to Use It?
&lt;/h2&gt;

&lt;p&gt;The entire project is &lt;strong&gt;production-ready&lt;/strong&gt; and open source on GitHub: &lt;a href="https://github.com/Tetianamost/aws-intelligent-document-processing" rel="noopener noreferrer"&gt;aws-intelligent-document-processing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Start:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repo&lt;/span&gt;
git clone https://github.com/Tetianamost/aws-intelligent-document-processing.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aws-intelligent-document-processing

&lt;span class="c"&gt;# 2. Deploy to AWS (takes ~3 minutes)&lt;/span&gt;
sam build
sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;

&lt;span class="c"&gt;# 3. Upload a document&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;your-document.png s3://your-bucket-name/incoming/

&lt;span class="c"&gt;# 4. Check DynamoDB for extracted data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything you need is included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Lambda function code&lt;/li&gt;
&lt;li&gt;✅ CloudFormation templates&lt;/li&gt;
&lt;li&gt;✅ Sample documents (invoice + tax form)&lt;/li&gt;
&lt;li&gt;✅ IAM permission templates&lt;/li&gt;
&lt;li&gt;✅ Step-by-step deployment guide&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Start with PNG images, not PDFs. Trust me on this one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or built something similar? I'd love to hear about it! Drop a comment or reach out.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>machinelearning</category>
      <category>lambda</category>
    </item>
    <item>
      <title>How I automated Certificate expiration alerts with AWS</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Sun, 12 Jan 2025 22:00:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10</link>
      <guid>https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10</guid>
      <description>&lt;p&gt;Recently I faced a challenge with certificate rotation. While solving it, I discovered a neat way to automate certificate rotation alerts using AWS services. Although ACM Private Certificate Authority (PCA) with KMS can be used for signing certificates, in some cases a simpler solution using Secrets Manager might be more appropriate for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS Account&lt;/li&gt;
&lt;li&gt;Basic understanding of AWS services&lt;/li&gt;
&lt;li&gt;AWS CLI configured (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;For our simple signing certificate use case, we chose to store certificates in AWS Secrets Manager. This brought a new challenge: how to track expiration dates and ensure timely rotation?&lt;/p&gt;

&lt;p&gt;Manual tracking of secret expiration dates is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error-prone&lt;/li&gt;
&lt;li&gt;Easy to forget&lt;/li&gt;
&lt;li&gt;Time-consuming&lt;/li&gt;
&lt;li&gt;Stressful (nobody wants expired certificates in production!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;I created a simple automation using three AWS services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EventBridge for scheduling&lt;/li&gt;
&lt;li&gt;Lambda for checking expiration&lt;/li&gt;
&lt;li&gt;SNS for notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; checks secrets periodically&lt;/li&gt;
&lt;li&gt;When a secret is nearing expiration, &lt;strong&gt;EventBridge&lt;/strong&gt; triggers notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS&lt;/strong&gt; sends alerts to the team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the Lambda function that does the magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenSSL.crypto&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;secretsmanager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;secretsmanager&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Get list of secrets
&lt;/span&gt;        &lt;span class="n"&gt;secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secretsmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_secrets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SecretList&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="c1"&gt;# Get the secret value
&lt;/span&gt;            &lt;span class="n"&gt;secret_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secretsmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SecretId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;secret_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SecretString&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

            &lt;span class="c1"&gt;# Look for certificate entries (ending in .cert)
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;secret_dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.cert&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="c1"&gt;# Parse the certificate
&lt;/span&gt;                    &lt;span class="n"&gt;cert_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenSSL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_certificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;OpenSSL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILETYPE_PEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                        &lt;span class="n"&gt;cert_data&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;# Get expiration date
&lt;/span&gt;                    &lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_notAfter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y%m%d%H%M%SZ&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;# Calculate days until expiration
&lt;/span&gt;                    &lt;span class="n"&gt;days_until_expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;

                    &lt;span class="c1"&gt;# Alert if expiring within 60 days
&lt;/span&gt;                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;days_until_expiry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;TopicArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:sns:REGION:ACCOUNT_ID:secret-rotation-alerts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Certificate Expiration Alert - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
                            Certificate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in secret &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is expiring soon!

                            Expiration Date: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
                            Days until expiration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;days_until_expiry&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

                            Please rotate this certificate soon.
                            &lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Certificate check completed successfully&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Error checking certificates: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting It Up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create an IAM Role for Lambda
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to IAM Console&lt;/li&gt;
&lt;li&gt;Click "Roles" → "Create role"&lt;/li&gt;
&lt;li&gt;Select "AWS Service" and choose "Lambda"&lt;/li&gt;
&lt;li&gt;Add following permissions (Create new policy):
&lt;/li&gt;
&lt;/ol&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"secretsmanager:ListSecrets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:DescribeSecret"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"sns:Publish"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sns:REGION:ACCOUNT_ID:secret-rotation-alerts"&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="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:*"&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="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;ol&gt;
&lt;li&gt;Name the role &lt;code&gt;secret-rotation-checker-role&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2 Create SNS Topic:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws sns create-topic &lt;span class="nt"&gt;--name&lt;/span&gt; secret-rotation-alerts
   aws sns subscribe &lt;span class="nt"&gt;--topic-arn&lt;/span&gt; &amp;lt;your-topic-arn&amp;gt; &lt;span class="nt"&gt;--protocol&lt;/span&gt; email &lt;span class="nt"&gt;--notification-endpoint&lt;/span&gt; your@email.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, confirm subscription via email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create Lambda Function
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to Lambda Console&lt;/li&gt;
&lt;li&gt;Click "Create function"&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author from scratch&lt;/li&gt;
&lt;li&gt;Function name: &lt;code&gt;secret-rotation-checker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runtime: Python 3.9&lt;/li&gt;
&lt;li&gt;Architecture: x86_64&lt;/li&gt;
&lt;li&gt;Permissions: Use existing role that we’ve created in previous steps&lt;code&gt;secret-rotation-checker-role&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste the Lambda code above and remember to replace &lt;code&gt;REGION&lt;/code&gt; and &lt;code&gt;ACCOUNT_ID&lt;/code&gt; with your values then click "Deploy".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Create EventBridge Rule
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to EventBridge Console&lt;/li&gt;
&lt;li&gt;Click "Rules" → "Create rule"&lt;/li&gt;
&lt;/ol&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%2F9hcb6dw02xbmi4oq5oh0.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%2F9hcb6dw02xbmi4oq5oh0.png" alt="Image description" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Settings:

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;quarterly-secret-check&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Description: "Check secrets every quarter for rotation"&lt;/li&gt;
&lt;li&gt;Rule type: Schedule&lt;/li&gt;
&lt;li&gt;Occurrence: Recurring schedule&lt;/li&gt;
&lt;li&gt;Schedule type: Cron-based schedule&lt;/li&gt;
&lt;li&gt;Cron: &lt;code&gt;0 9 1 1,4,7,10 ? *&lt;/code&gt; (Runs quarterly)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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%2Fodr22kd0xgmamrmictth.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%2Fodr22kd0xgmamrmictth.png" alt="Image description" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select target:

&lt;ul&gt;
&lt;li&gt;Target type: AWS Service&lt;/li&gt;
&lt;li&gt;Templated targets: Lambda Invoke&lt;/li&gt;
&lt;li&gt;Function: &lt;code&gt;secret-rotation-checker&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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%2Fkzxqwjirn4ebpj5g27dq.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%2Fkzxqwjirn4ebpj5g27dq.png" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why every 3 months? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most secrets/certificates are valid for 1 year&lt;/li&gt;
&lt;li&gt;Quarterly checks provide enough time to act&lt;/li&gt;
&lt;li&gt;Reduces unnecessary Lambda executions&lt;/li&gt;
&lt;li&gt;Still catches any missed rotations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Test the Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go back to Lambda console&lt;/li&gt;
&lt;li&gt;Select your function&lt;/li&gt;
&lt;li&gt;Click "Test" tab&lt;/li&gt;
&lt;li&gt;Create new test event (empty &lt;code&gt;{}&lt;/code&gt; is fine)&lt;/li&gt;
&lt;li&gt;Click "Test"&lt;/li&gt;
&lt;li&gt;Check CloudWatch logs for execution results&lt;/li&gt;
&lt;li&gt;Check your email for any alerts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! Now you'll get email alerts when secrets need rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization Options
&lt;/h2&gt;

&lt;p&gt;The Lambda function can be customized in several way. You can modify the code to check specific secrets by filtering them using tags, which is useful for managing different types of secrets separately. If you need earlier notifications, you can adjust the alert threshold from the current 330 days to any number that suits your rotation schedule. The notification system can be expanded beyond email by adding more SNS subscription types like Slack or Microsoft Teams. For more detailed tracking, you can enhance the alerts by including additional secret metadata and details in the notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;p&gt;This automation solution is designed to be extremely cost-effective. Since the Lambda function only executes quarterly (4 times per year), the Lambda costs are minimal. SNS notifications are only triggered when secrets actually need rotation, making the messaging costs negligible. CloudWatch logs are kept minimal, only storing essential execution information, which keeps logging costs low. Overall, the entire setup typically costs less than a dollar per month, making it a cost-effective solution for maintaining security compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Comparison
&lt;/h2&gt;

&lt;p&gt;While this blog post demonstrates a simple solution using Secrets Manager, it's worth mentioning that AWS offers other approaches for certificate management:&lt;/p&gt;

&lt;p&gt;ACM Private Certificate Authority (PCA) with KMS could be used for signing certificates, but it involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up and maintaining a CA hierarchy&lt;/li&gt;
&lt;li&gt;Higher costs for standard CA (~$400/month per CA)

&lt;ul&gt;
&lt;li&gt;A cheaper short-lived certificate mode exists ($50/month) but it's designed for certificates with short validity periods (hours/days)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;More complex integration with KMS for signing operations&lt;/li&gt;

&lt;li&gt;Better suited for enterprise-wide PKI needs or environments requiring frequent certificate rotation&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Our Secrets Manager solution is ideal when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple certificate storage and rotation&lt;/li&gt;
&lt;li&gt;Direct key access for signing operations&lt;/li&gt;
&lt;li&gt;Cost-effective solution (few dollars per month)&lt;/li&gt;
&lt;li&gt;Quick implementation with minimal setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose the approach that best fits your specific needs and scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking It Further
&lt;/h2&gt;

&lt;p&gt;Want to automate the actual rotation? AWS Secrets Manager has a built-in rotation feature for some secret types. For custom secrets, you can write another Lambda function that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates new secrets&lt;/li&gt;
&lt;li&gt;Updates applications&lt;/li&gt;
&lt;li&gt;Verifies everything works&lt;/li&gt;
&lt;li&gt;Archives old secrets&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But that's a topic for another post! 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Implementing this automation has taught me several valuable lessons. First and foremost, automating secret rotation tracking is significantly more reliable than manual tracking methods, eliminating human error and reducing operational overhead. The seamless integration between AWS services (Lambda, EventBridge, and SNS) demonstrates how powerful cloud services can be when used together. While the code implementation is relatively simple, it saves countless hours that would otherwise be spent on manual checks and tracking. Most importantly, this automation provides peace of mind – you can rest easier knowing that your secrets rotation schedule is being monitored automatically, and you'll receive timely notifications when action is needed.&lt;/p&gt;

&lt;p&gt;Hope this helps someone else automate their secret management! Let me know if you have questions or suggestions for improvements.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Understanding RabbitMQ Brokers in AWS</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Fri, 13 Dec 2024 23:32:21 +0000</pubDate>
      <link>https://dev.to/aws-builders/understanding-rabbitmq-brokers-in-aws-6d3</link>
      <guid>https://dev.to/aws-builders/understanding-rabbitmq-brokers-in-aws-6d3</guid>
      <description>&lt;h2&gt;
  
  
  What's a Message Broker?
&lt;/h2&gt;

&lt;p&gt;A message broker is like a post office for your applications. Just as a post office manages the flow of mail between senders and receivers, a message broker manages the flow of messages between different parts of your system. They're called "brokers" because they broker (negotiate and manage) the communication between parties.&lt;/p&gt;

&lt;p&gt;RabbitMQ is one type of message broker that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepts messages from producers&lt;/li&gt;
&lt;li&gt;Decides where to route them&lt;/li&gt;
&lt;li&gt;Delivers them to consumers&lt;/li&gt;
&lt;li&gt;Manages message storage when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up a Broker in AWS
&lt;/h2&gt;

&lt;p&gt;AWS makes this easy through Amazon MQ. Here's how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Console → Amazon MQ&lt;/li&gt;
&lt;li&gt;Click "Create broker"&lt;/li&gt;
&lt;li&gt;Choose engine type: RabbitMQ
&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%2Fvn8gosaoc6hy2zubdqvp.png" alt="Image description" width="800" height="259"&gt;
&lt;/li&gt;
&lt;li&gt;Select deployment: Single-instance (dev) or Cluster (prod)&lt;/li&gt;
&lt;/ul&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%2Fibnqxoq4a83b0mzc9f4t.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%2Fibnqxoq4a83b0mzc9f4t.png" alt="Image description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose broker name&lt;/li&gt;
&lt;li&gt;Pick broker instance type (like mq.m5.large)&lt;/li&gt;
&lt;li&gt;Set up username/password&lt;/li&gt;
&lt;/ul&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%2Fzkjfok2y1ur5akrmi50m.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%2Fzkjfok2y1ur5akrmi50m.png" alt="Image description" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure network in Advanced Settings Details (select VPC and subnets)&lt;/li&gt;
&lt;/ul&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%2Fofao094asn4vjtjqweau.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%2Fofao094asn4vjtjqweau.png" alt="Image description" width="800" height="699"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review and create&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creation, AWS provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint for connections&lt;/li&gt;
&lt;li&gt;Console URL for management&lt;/li&gt;
&lt;li&gt;Monitoring through CloudWatch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three Important Details About AWS RabbitMQ Brokers:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network Type:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private: Only accessible within VPC (recommended)&lt;/li&gt;
&lt;li&gt;Public: Accessible from internet (use carefully)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Control:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username/password for connections&lt;/li&gt;
&lt;li&gt;Security groups for network access&lt;/li&gt;
&lt;li&gt;IAM for AWS console access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudWatch metrics included&lt;/li&gt;
&lt;li&gt;Broker health status&lt;/li&gt;
&lt;li&gt;Connection monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Choose private access and proper security groups when starting out. You can always modify access later.&lt;/p&gt;

&lt;p&gt;Remember: A broker is just a messenger - it needs proper setup to deliver messages reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Note
&lt;/h2&gt;

&lt;p&gt;AWS charges for broker uptime, not message volume. Start with smaller instances for development.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>development</category>
    </item>
    <item>
      <title>Cracking the AWS Certified Database - Specialty Exam: My Tips and Tricks</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Fri, 03 May 2024 19:17:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/cracking-the-aws-certified-database-specialty-exam-my-tips-and-tricks-12b3</link>
      <guid>https://dev.to/aws-builders/cracking-the-aws-certified-database-specialty-exam-my-tips-and-tricks-12b3</guid>
      <description>&lt;p&gt;Hey everyone,&lt;br&gt;
I recently passed the AWS Certified Database - Specialty (DBS-C01) exam, and I wanted to share my experience and some tips that helped me along the way. If you're planning to take this exam, keep reading!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Know the Exam Guide:&lt;br&gt;
First things first, make sure you read the &lt;a href="https://d1.awsstatic.com/training-and-certification/docs-database-specialty/AWS-Certified-Database-Specialty_Exam-Guide.pdf"&gt;AWS Certified &lt;br&gt;
Database - Specialty (DBS-C01) Exam Guide&lt;/a&gt;. It tells you &lt;br&gt;
exactly what services and topics you need to know before &lt;br&gt;
taking the exam. Trust me, it's a lifesaver!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use AWS FAQs and Whitepapers:&lt;br&gt;
AWS has a ton of FAQs and &lt;a href="https://repost.aws/questions?view=all&amp;amp;search=databases&amp;amp;sort=relevant"&gt;whitepapers&lt;/a&gt; that can help you &lt;br&gt;
prepare. Focus on the FAQs for the services mentioned in the &lt;br&gt;
exam guide. They often have information that can help you &lt;br&gt;
answer exam questions. And don't forget to check out the &lt;br&gt;
whitepapers for a deeper understanding of database services &lt;br&gt;
and best practices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Practice with Tutorial Dojo:&lt;br&gt;
&lt;a href="https://portal.tutorialsdojo.com/product-category/aws-practice-exams/"&gt;Tutorial Dojo&lt;/a&gt; is awesome for practice questions. What I love &lt;br&gt;
about them is that they explain why each answer is right or &lt;br&gt;
wrong. They also have cheat sheets and other helpful &lt;br&gt;
resources. Definitely check them out!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use free exam topics questions:&lt;br&gt;
Head over to &lt;a href="https://www.examtopics.com/exams/amazon/aws-certified-database-specialty/"&gt;examtopics.com&lt;/a&gt; and join the discussions. Even &lt;br&gt;
though they might not give you the exact answers, following &lt;br&gt;
other people discussions who have taken the exam can be &lt;br&gt;
super helpful. &lt;br&gt;
You can learn from their experiences and get a better idea &lt;br&gt;
of what to focus on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exam Day Tips:&lt;/p&gt;

&lt;p&gt;If you're taking the online exam from home, make sure you have a valid ID, a clean desk, and a quiet room. You can only use one screen during the exam.&lt;/p&gt;

&lt;p&gt;Be ready for a lot of questions on &lt;a href="https://aws.amazon.com/timestream/"&gt;TimeStream DB&lt;/a&gt;. Make sure you know this topic well.&lt;br&gt;
Expect some tough questions on database migration and design, especially moving from on-premises to the cloud.&lt;br&gt;
Don't panic if you come across difficult questions. Remember, there are 15 questions that don't count towards your score, but you won't know which ones they are. Let's hope it's the hardest ones and do your best on each question.&lt;/p&gt;

&lt;p&gt;Preparing for the AWS Certified Database - Specialty exam takes time and effort, but with the right resources and practice, you can totally do it! Follow the tips I mentioned and use the study materials I suggested. Even if you feel unsure during the exam, trust yourself and keep going. When you pass, you'll feel so proud of yourself, and it will all be worth it. You've got this!&lt;br&gt;
Good luck on your exam!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Handling EKS: kubectl, Namespaces, and CRDs</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Sun, 04 Feb 2024 20:19:40 +0000</pubDate>
      <link>https://dev.to/aws-builders/handling-eks-habitats-kubectl-namespaces-and-crds-nff</link>
      <guid>https://dev.to/aws-builders/handling-eks-habitats-kubectl-namespaces-and-crds-nff</guid>
      <description>&lt;p&gt;Managing EKS clusters from the command line can seem daunting at first, but having a grasp of some key kubectl commands will make you an expert in no time. In this beginner's guide, I'll walk through the most useful CLI commands for inspecting and troubleshooting your EKS workloads, nodes, namespaces and CRDs.&lt;br&gt;
Let's start by looking at namespaces - these allow you to logically organize your cluster resources. To see current namespaces:&lt;br&gt;
&lt;code&gt;kubectl get namespaces&lt;/code&gt;&lt;br&gt;
You can then specify a namespace when running commands:&lt;br&gt;
&lt;code&gt;kubectl get rs -n prod&lt;/code&gt; # rs  stands for replica-set&lt;br&gt;
This isolates prod resources from dev and test. Handy for access control between teams!&lt;/p&gt;

&lt;p&gt;Here's a trick to set a default namespace so you don't have to enter it each time:&lt;br&gt;
&lt;code&gt;kubectl config set-context --current --namespace=dev&lt;/code&gt;&lt;br&gt;
This will set the default namespace going forward to "dev". Now all kubectl commands will apply to that namespace by default:&lt;br&gt;
&lt;code&gt;kubectl describe secrets aws-secret # Describes aws-secret in dev namespace&lt;/code&gt;&lt;br&gt;
And you can still override the default when needed:&lt;br&gt;
&lt;code&gt;kubectl get pods -n kube-system&lt;/code&gt;&lt;br&gt;
One specially important namespace is kube-system. This houses critical cluster services and addons like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CoreDNS - Cluster DNS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VPC Networking plugins&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node Termination Handler&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observe but don't modify kube-system to avoid issues!&lt;br&gt;
Now, let's inspect pod workloads, the basic building blocks of EKS services:&lt;br&gt;
&lt;code&gt;kubectl get pods -n dev&lt;/code&gt;&lt;br&gt;
This lists all pods in dev namespace and their status. To get more info on a specific pod:&lt;br&gt;
&lt;code&gt;kubectl describe node &amp;lt;node-name&amp;gt;&lt;/code&gt;&lt;br&gt;
See the pattern here? "Get" for an overview, and "describe" for the nitty gritty details. This works for all Kubernetes resource types (e.g. &lt;code&gt;kubectl get nodes&lt;/code&gt;, &lt;code&gt;kubectl describe node &amp;lt;name&amp;gt;&lt;/code&gt; etc)&lt;br&gt;
Finally, you can actually shell into a container with exec:&lt;br&gt;
&lt;code&gt;kubectl exec -it &amp;lt;pod-name&amp;gt; -- bash&lt;/code&gt;&lt;br&gt;
This drops you into a bash shell running inside the pod, where you can inspect files, env vars, check connections, and troubleshoot further.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inspecting Custom Resources&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many EKS addons and controllers introduce their own CustomResourceDefinitions (CRDs) - custom Kubernetes object types defined by scripts rather than baked into the main codebase.&lt;br&gt;
For example, the AWS Load Balancer Controller has a CRD for defining Application Load Balancers.&lt;br&gt;
You can view custom resources by running:&lt;br&gt;
&lt;code&gt;kubectl get crds&lt;/code&gt;&lt;br&gt;
This will list all CRDs registered on the cluster. You can then operate on the custom resources themselves:&lt;br&gt;
kubectl get albingress -n app-ns`&lt;br&gt;
Here we are listing ingresses from the Application Load Balancer CRD.&lt;br&gt;
CRDs allow developers to extend Kubernetes functionality without having to rebuild the core API. This enables rapid innovation. Definitely important to be aware of CRDs when running addons that enhance EKS!&lt;/p&gt;

&lt;p&gt;As you can see, kubectl makes it easy to unlock the power of EKS! With these basic commands, you'll gain visibility and control over your clusters. The official &lt;a href="https://kubernetes.io/docs/reference/kubectl/quick-reference/"&gt;kubectl cheat sheet&lt;/a&gt; lists even more goodies to try out.&lt;/p&gt;

&lt;p&gt;Happy Kubectl-ing!&lt;/p&gt;

</description>
      <category>eks</category>
      <category>aws</category>
      <category>kubectl</category>
    </item>
    <item>
      <title>Use Go Lambda and API Gateway to Generate DynamoDB Tables on Demand</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Sun, 10 Sep 2023 20:48:54 +0000</pubDate>
      <link>https://dev.to/aws-builders/use-go-lambda-and-api-gateway-to-generate-dynamodb-tables-on-demand-kj2</link>
      <guid>https://dev.to/aws-builders/use-go-lambda-and-api-gateway-to-generate-dynamodb-tables-on-demand-kj2</guid>
      <description>&lt;p&gt;In my previous &lt;a href="https://dev.to/aws-builders/creating-an-aws-lambda-function-in-go-and-troubleshooting-common-errors-2k6h"&gt;post&lt;/a&gt;, we focused on setting up Go correctly for Lambda development, zipping and uploading our function and troubleshooting issues like missing files. Now let's look at actually building our function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build GoLang Lambda Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This code is a Go function that creates a DynamoDB table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "context"
    "log"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/dynamodb"
)

func createDynamoDBTable(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    sess := session.Must(session.NewSessionWithOptions(session.Options{
        SharedConfigState: session.SharedConfigEnable,
    }))

    // Create DynamoDB client
    svc := dynamodb.New(sess)

    // Define the table name, attribute definitions, key schema, and provisioned throughput
    tableName := "Movies"
    input := &amp;amp;dynamodb.CreateTableInput{
        AttributeDefinitions: []*dynamodb.AttributeDefinition{
            {
                AttributeName: aws.String("Year"),
                AttributeType: aws.String("N"),
            },
            {
                AttributeName: aws.String("Title"),
                AttributeType: aws.String("S"),
            },
        },
        KeySchema: []*dynamodb.KeySchemaElement{
            {
                AttributeName: aws.String("Year"),
                KeyType:       aws.String("HASH"),
            },
            {
                AttributeName: aws.String("Title"),
                KeyType:       aws.String("RANGE"),
            },
        },
        ProvisionedThroughput: &amp;amp;dynamodb.ProvisionedThroughput{
            ReadCapacityUnits:  aws.Int64(10),
            WriteCapacityUnits: aws.Int64(10),
        },
        TableName: aws.String(tableName),
    }

    // Create the DynamoDB table
    _, err := svc.CreateTable(input)
    if err != nil {
        log.Fatalf("Got error calling CreateTable: %s", err)
        return events.APIGatewayProxyResponse{
            StatusCode: 500,
            Body:       "Error creating DynamoDB table",
        }, err
    }

    return events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       "Created the table " + tableName,
    }, nil
}

func main() {
    lambda.Start(createDynamoDBTable)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we import Go packages to use DynamoDB and Lambda.&lt;/p&gt;

&lt;p&gt;The func keyword declares our function. The ctx parameter provides "context" - extra information like time limits for the function. Ctx carries details such as the time left before the function shuts down automatically. It also contains data about the request and response between Lambda and API Gateway. You can think of ctx as getting the party location and time so you know when and where it's happening and be there on time at the right place.&lt;/p&gt;

&lt;p&gt;Inside the function we use the AWS Go SDK to connect to DynamoDB. We call code from other packages using dot notation like aws.String.&lt;/p&gt;

&lt;p&gt;Then we create a Movies table with Year and Title columns. &lt;br&gt;
The imported packages are like toolkits that let us use DynamoDB and Lambda. We import them so our code can work with these AWS services.&lt;/p&gt;

&lt;p&gt;The createDynamoDBTable function then uses the toolkits to connect to DynamoDB. It creates the table by calling the CreateTable method from the toolkit.&lt;/p&gt;

&lt;p&gt;main() runs first when executed. It starts our table creation function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test our function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After we zipped and uploaded the function (see detailed steps &lt;a href="https://dev.to/aws-builders/creating-an-aws-lambda-function-in-go-and-troubleshooting-common-errors-2k6h"&gt;here&lt;/a&gt;), let's test it to make sure that it's working as expected:&lt;br&gt;
&lt;a href="https://media.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%2F5mi3lr9xcp7c316z9o68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F5mi3lr9xcp7c316z9o68.png" alt="Test Lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're looking for the successful result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fmyzf3ff8od137gi5zx0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fmyzf3ff8od137gi5zx0f.png" alt="Success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: AWS may create resources for you to simulate the execution environment. If you'd like to invoke your Lambda function using API Gateway, please, remove DynamoDB table that was created in this step.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.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%2Faemky3ofh9p17d670xoc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Faemky3ofh9p17d670xoc.png" alt="Delete table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add API Gateway to invoke Lambda&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the API Gateway service and click on "Create API" to start creating a new API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose "HTTP API" or "REST API" depending on your requirements. HTTP APIs are suitable for simpler use cases, and that's what we'll use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define Your API's integration and Routes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose our "Lambda Function" as the integration type. This is the function that will be invoked when this API endpoint is accessed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fyp8mvdpl28d3ha6g7h9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fyp8mvdpl28d3ha6g7h9m.png" alt="Integration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we'll add a Route. Routes define how incoming requests are mapped to the associated API Gateway resources and integrations. Each route can have different configurations, including the integration target (e.g., a Lambda function), request/response transformations, and authorization settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fh816uc5uitth50g25o3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fh816uc5uitth50g25o3x.png" alt="Add POST Route"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our API Gateway will route POST /CreateDBTable requests to our Lambda function.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's choose default stage and proceed with creation process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.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%2F6usr2a6grxfx9irgkzzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F6usr2a6grxfx9irgkzzv.png" alt="Stage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoke API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to invoke our Lambda function through the API Gateway that we've configured with a POST route to /CreateDBTable, we can use a tool like curl or a similar HTTP client, or we can use a service like Postman. Here's how we can invoke our Lambda function using curl from the command line:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -X POST &amp;lt;base URL of our API Gateway stage&amp;gt; &amp;lt;the path to the POST route we've defined&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdg4mzzmvn7kuowlur3v5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdg4mzzmvn7kuowlur3v5.png" alt="Curl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And voilà! We have successfully created a DynamoDB table by invoking a Lambda function with API Gateway!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now you know how to build a Lambda function in Go that creates resources like DynamoDB tables. You also set up API Gateway to trigger it on demand.&lt;/p&gt;

&lt;p&gt;You've got the skills to make serverless functions using AWS and connect them to various services. This opens up lots of possibilities to build powerful applications!&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>dynamodb</category>
      <category>apigateway</category>
      <category>go</category>
    </item>
    <item>
      <title>Creating an AWS Lambda Function in Go and Troubleshooting Common Errors</title>
      <dc:creator>Tetiana Mostova</dc:creator>
      <pubDate>Wed, 06 Sep 2023 00:39:44 +0000</pubDate>
      <link>https://dev.to/aws-builders/creating-an-aws-lambda-function-in-go-and-troubleshooting-common-errors-2k6h</link>
      <guid>https://dev.to/aws-builders/creating-an-aws-lambda-function-in-go-and-troubleshooting-common-errors-2k6h</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Lambda is a powerful service that allows you to run your code without provisioning or managing servers. In this tutorial, we will walk through the process of creating an AWS Lambda function using the Go programming language (we are going to code a Lambda function in the next one). We'll also address a common error that you might encounter during this setup and provide solutions to resolve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before you begin, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account.&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured: You can log in to your AWS account and configure the AWS CLI by running the following command in your terminal:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;aws configure&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command will prompt you to enter your AWS Access Key ID, Secret Access Key, default region name, and output format.  These credentials can be obtained from your AWS account settings under &lt;em&gt;IAM &amp;gt; Users &amp;gt; Security credentials&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go programming language installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Setting up Go Modules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're working with Go, it's a good practice to use Go modules to manage your dependencies. Even for a single-file project, Go modules help maintain consistency and allow for future expansion.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In your project directory, initialize Go modules with your chosen module name:&lt;br&gt;
&lt;code&gt;go mod init DynamoDBCreateTable&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install any necessary dependencies using go get, if applicable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Building Your Lambda Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, let's create your Lambda function using Go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write your Lambda function code in a Go file (e.g., &lt;a href="https://dev.to/aws-builders/use-go-lambda-and-api-gateway-to-generate-dynamodb-tables-on-demand-kj2"&gt;DynamoDBCreateTable.go&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build your Go code for the Lambda environment:&lt;br&gt;
&lt;code&gt;GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bootstrap -tags lambda.norpc DynamoDBCreateTable.go&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: The resulting binary is named "bootstrap" because AWS Lambda expects this name for Go functions (read more &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/golang-package.html#golang-package-mac-linux"&gt;here&lt;/a&gt;). In many other programming languages, the handler for an AWS Lambda function is typically named as a method or function within your code. However, in Go, it should be specifically named "bootstrap".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've created the AWS Lambda function with the correct handler name, you can proceed with the next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Zip Your Go File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before deploying your AWS Lambda function, you'll need to package your Go code into a zip file. In your terminal, navigate to the directory containing your Go code file, which in our case is "bootstrap." Run the following command to create a zip file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zip DynamoDBCreateTable.zip bootstrap&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command will package your Go code into a zip file named "DynamoDBCreateTable.zip."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Create the Lambda Function&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy your Lambda function using the AWS CLI:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws lambda create-function \
--function-name CreateDBTable \
--runtime provided.al2 \
--handler bootstrap \
--architectures arm64 \
--role arn:aws:iam::11112222333:role/service-role/DynamoDBCreateTable**** \
--zip-file fileb://DynamoDBCreateTable.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;--function-name: Specify a name for your Lambda function.&lt;br&gt;
--runtime: Set the runtime to "provided.al2" for Go (recommended).&lt;br&gt;
--handler: Set the handler to "bootstrap" to match the Go entry point.&lt;br&gt;
--architectures: Specify the architecture as needed.&lt;br&gt;
--role: Provide the ARN of the IAM role with necessary permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting: Handling Common Errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you encounter the error message "fork/exec /var/task/bootstrap: no such file or directory: PathError," follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure that your Go code is correctly built for the Lambda environment.&lt;/li&gt;
&lt;li&gt;Verify that the handler in your aws lambda create-function command is set to "bootstrap".&lt;/li&gt;
&lt;li&gt;Double-check the IAM role's permissions to ensure it has access to the required AWS services.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building Lambda functions with Go is a powerful way to run serverless code while making deployment easy. Just follow these steps and watch out for common mistakes. Now you've got the skills to launch your own Lambda functions using AWS CLI and leverage AWS's serverless platform.&lt;/p&gt;

&lt;p&gt;Feel free to customize your code to connect with DynamoDB or other AWS services your app needs.&lt;/p&gt;

&lt;p&gt;Stay Tuned: In our next post, we'll dive deeper into Lambda functions. We'll walk through creating one that dynamically generates DynamoDB tables by calling an API Gateway. This will expand your serverless skills so you can build scalable, efficient apps on AWS.&lt;/p&gt;

&lt;p&gt;Keep coding and stay motivated!&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>go</category>
      <category>development</category>
      <category>troubleshooting</category>
    </item>
  </channel>
</rss>
