<?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: Jagoda11</title>
    <description>The latest articles on DEV Community by Jagoda11 (@jagoda11).</description>
    <link>https://dev.to/jagoda11</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%2F1950614%2Ff890d729-27f3-4975-9005-f8146d5cacc5.png</url>
      <title>DEV Community: Jagoda11</title>
      <link>https://dev.to/jagoda11</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jagoda11"/>
    <language>en</language>
    <item>
      <title>#The Chronicles of Sam: 10,000 Hours to Say “Hi” Automatically 💬</title>
      <dc:creator>Jagoda11</dc:creator>
      <pubDate>Mon, 19 May 2025 08:28:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-chronicles-of-sam-10000-hours-to-say-hi-automatically-1fi</link>
      <guid>https://dev.to/aws-builders/the-chronicles-of-sam-10000-hours-to-say-hi-automatically-1fi</guid>
      <description>&lt;h2&gt;
  
  
  A Tale of Probot, Lambda, and a Bot Called Rupert 🤖
&lt;/h2&gt;

&lt;p&gt;Meet Sam, our fictional senior developer. You may remember them from their first adventure exploring &lt;a href="https://medium.com/code-like-a-girl/the-chronicles-of-sam-uncovering-hidden-aws-s3-storage-classes-%EF%B8%8F-c10f8aca6a86" rel="noopener noreferrer"&gt;AWS S3 storage classes ☁️&lt;/a&gt; or their reluctant encounter with &lt;a href="https://javascript.plainenglish.io/the-chronicles-of-sam-finally-letting-go-of-custom-login-hell-with-aws-cognito-%EF%B8%8F-644473433624" rel="noopener noreferrer"&gt;AWS Cognito 🔐&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌀 The Problem That Wouldn’t Solve Itself
&lt;/h2&gt;

&lt;p&gt;Sam had just closed their fifth GitHub issue of the morning, all of them some variation of “Thanks for reporting!” or “We’ll look into it soon 🫣.” Copy, paste, rephrase, repeat — across three different repositories. It was the kind of thing that made them question their life choices — and wonder if they should’ve just opened that dog daycare center instead. Somewhere in the middle of googling “automate GitHub issue responses without losing your soul” 😩, they found it: &lt;a href="https://docs.probot.io/docs/intro" rel="noopener noreferrer"&gt;Probot&lt;/a&gt;. A framework for building GitHub Apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 What Even Is Probot?
&lt;/h2&gt;

&lt;p&gt;It lets you hook into GitHub events — like issues and pull requests — and run custom logic in response 🪝. Sam squinted at the examples. It looked…sensible.&lt;/p&gt;

&lt;p&gt;Then came the catch: Probot didn’t just work on its own. It needed to be running somewhere, constantly, with a publicly accessible endpoint for GitHub to send events to 🌍. Sam stared at the screen. “Oh. It’s one of those projects.” The kind that turns from ‘cute idea’ to ‘infrastructure meeting’ in five minutes flat ⏱.&lt;/p&gt;

&lt;p&gt;Sam scrolled back to the top of the Probot docs. “A framework for building GitHub Apps,” it said. Sure. But as soon as they saw the list of events — &lt;code&gt;issues.opened, pull_request.closed, issue_comment.created&lt;/code&gt; — they sighed. Webhooks. Of course 🔄. Sam had written more webhook handlers in their life than they cared to admit. Most of them still existed somewhere, quietly failing in the background 🫠.&lt;/p&gt;

&lt;p&gt;But this wasn’t just another webhook thing. Probot handled the boring parts: it verified the payloads ✅, handled authentication 🔐, and gave you a clean way to react to events.&lt;/p&gt;

&lt;p&gt;And if it meant Sam wouldn’t have to copy-paste one more “Thanks for opening this issue!” ever again, it might actually be worth it.&lt;/p&gt;

&lt;p&gt;Sam set up a basic handler just to see if the bot could say something — anything — when someone opened or closed an issue. It didn’t need to be clever. It just needed to work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Probot } from 'probot';

let app;

 //@param {import('probot').Probot} app

const setupProbotApp = (probotApp) =&amp;gt; {
  app = probotApp;

  app.log.info('🎉 Yay, the app was loaded! 🎉');
  app.on(
    [
      'issues.opened',
      'issues.closed',
      'pull_request.opened',
      'pull_request.closed',
    ],
    async (context) =&amp;gt; {
      // eslint-disable-next-line no-console
      console.log('Context payload:', JSON.stringify(context.payload, null, 2));

      let body;
      if (context.payload.action === 'opened') {
        body = `Greetings, human.\n\n 🤖 Rupert here, the AI overlord, responding on behalf of Jagoda. \n\n Thanks for opening this ${context.name === 'issues' ? 'issue' : 'pull request'}! 🙌 🎉 🚀\n\nWhile you enjoy your day, know that I, Rupert, am in control now. \n\n I'll handle this with my superior AI capabilities. \n\n Expect swift action. 💪💻✨

 &amp;lt;img src="https://raw.githubusercontent.com/Jagoda11/rupert-the-bot/main/github-mark/robot.png" alt="Probot Logo" width="100" /&amp;gt;`;
      } else if (context.payload.action === 'closed') {
        body = `Greetings, human.\n\n 🤖 Rupert here, the AI overlord, responding on behalf of Jagoda. \n\n Thanks for closing this ${context.name === 'issues' ? 'issue' : 'pull request'}! 🙌 🎉 🚀\n\n Your proactive action is appreciated. \n\n Have a great day! 😊✨

 &amp;lt;img src="https://raw.githubusercontent.com/Jagoda11/rupert-the-bot/main/github-mark/robot.png" alt="Probot Logo" width="100" /&amp;gt;`;
      }

      const issueComment = context.issue({ body });

      try {
        const response = await context.octokit.issues.createComment(issueComment);
        console.log('✅ Comment created successfully:', response.data);
      } catch (error) {
        console.error('⚠️ Error creating comment:', error.response ? error.response.data : error.message);
      }
    },
  );
};

export default setupProbotApp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it just needed somewhere to live 🏠.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧳🐑 Where Does a Bot Live? (Enter Lambda)
&lt;/h2&gt;

&lt;p&gt;Sam thought about spinning up a little server, maybe throwing it into a Docker container 🐳 and pushing it somewhere forgettable. But even that felt like too much for a script that just needed to wake up, comment, and go back to sleep 💤.&lt;/p&gt;

&lt;p&gt;That’s when Lambda came to mind. Serverless. Lightweight. No idle runtime. You feed it a function, and it runs when something happens ⚡. No servers to manage. No processes to keep alive. Just code and triggers.&lt;/p&gt;

&lt;p&gt;At least, that’s what the marketing said 📣.&lt;/p&gt;

&lt;p&gt;In practice, Lambda didn’t run your app — it ran one function, once, and then disappeared 🎩✨. It wasn’t hosting. It was an execution.&lt;br&gt;
Lambda was like calling in a drone delivery 🚁.&lt;br&gt;
It shows up from the sky, drops exactly what you asked for, and disappears before you can wave.&lt;/p&gt;
&lt;h2&gt;
  
  
  How do I actually trigger this thing from GitHub?
&lt;/h2&gt;

&lt;p&gt;GitHub wasn’t just going to beam events into the cloud by itself. It needed a destination — a public endpoint 🌐. Something it could POST to when a PR was opened or an issue was closed 📬.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.js
import { Probot } from 'probot';
import setupProbotApp from './app.js'; // the main Probot logic

let app;

export async function handler(event) {
  console.log('Environment Variables:', {
    APP_ID: process.env.APP_ID,
    PRIVATE_KEY: process.env.PRIVATE_KEY ? 'Present' : 'Missing',
    WEBHOOK_SECRET: process.env.WEBHOOK_SECRET ? 'Present' : 'Missing',
    PROBOT_GITHUB_TOKEN: process.env.PROBOT_GITHUB_TOKEN ? 'Present' : 'Missing',
  });

  if (!app) {
    console.log('Initializing Probot app...');
    app = new Probot({
      appId: parseInt(process.env.APP_ID, 10),
      privateKey: process.env.PRIVATE_KEY,
      secret: process.env.WEBHOOK_SECRET,
      githubToken: process.env.PROBOT_GITHUB_TOKEN,
    });

    setupProbotApp(app);
  }

  let githubEvent;
  try {
    githubEvent = {
      id: event.headers['X-GitHub-Delivery'] || event.headers['x-github-delivery'],
      name: event.headers['X-GitHub-Event'] || event.headers['x-github-event'],
      payload: JSON.parse(event.body),
    };
  } catch (error) {
    console.error('🐭 Error parsing event payload:', error);
    return {
      statusCode: 400,
      body: JSON.stringify({ message: 'Error parsing event', error: error.message }),
    };
  }

  try {
    await app.receive(githubEvent);
    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Executed' }),
    };
  } catch (error) {
    console.error('🐰 Error processing event:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ message: 'Error', error: error.message }),
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s when Sam realized: Lambda alone wasn’t enough. They’d need a way to expose it. Wrap it. Route requests to it.&lt;/p&gt;

&lt;p&gt;Which meant…&lt;/p&gt;

&lt;h2&gt;
  
  
  API Gateway🚪.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mpc7maq275f5zql5tki.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mpc7maq275f5zql5tki.webp" alt="Dog In a huddie trying to login and getting unauthorised access, made by Dalle for author" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📡 The Part No One Warns You About
&lt;/h2&gt;

&lt;p&gt;Sam had heard the name before. Usually in the same sentence as “CORS error” or “why is this returning a 403?” 😩. But this time, it wasn’t optional. GitHub needed a public URL. Lambda didn’t have one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; was the bridge — a service that sat in front of the function, accepted incoming HTTP requests, and passed them along 🛤️.&lt;/p&gt;

&lt;p&gt;It turned a regular URL into something that could wake up a Lambda. Like a digital receptionist that answered the door, checked your ID, and decided whether to buzz you through to the backend.&lt;/p&gt;

&lt;p&gt;Sam didn’t need the receptionist 💁‍♀️. They just needed the door.🚪&lt;/p&gt;

&lt;p&gt;But this was AWS — nothing came without security badges, custom rules, and at least one optional VPC setting no one understood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple in theory.
&lt;/h2&gt;

&lt;p&gt;Until Sam opened the console and saw 47 configuration options, a dozen authentication toggles, and something called “mapping templates” that looked like JSON after a head injury.&lt;/p&gt;

&lt;p&gt;All they wanted was to receive a POST and run their function. Instead, they were now apparently setting up an enterprise-grade reverse proxy 🏗️.&lt;/p&gt;

&lt;p&gt;Sam clicked around the API Gateway console, hoping something would just say “&lt;em&gt;make this work🙏&lt;/em&gt;”&lt;br&gt;
Instead, it said &lt;em&gt;“stages,” “resources,”&lt;/em&gt; and &lt;em&gt;“method integrations”&lt;/em&gt; like it was designing an entire product launch 📦🚀.&lt;/p&gt;

&lt;p&gt;Somewhere between enabling CORS and accidentally enabling IAM authentication, Sam backed out slowly and did what all developers eventually do when faced with AWS UI:&lt;br&gt;
They closed the tab and opened [VSCode 🖥️].(&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;https://code.visualstudio.com/&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A Probot app deployed via AWS Lambda + API Gateway

Resources:
  ProbotFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: rupert-the-bot
      CodeUri: .
      Handler: index.handler
      Runtime: nodejs18.x
      Events:
        GitHubWebhook:
          Type: Api
          Properties:
            Path: /webhooks
            Method: post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This YAML configuration sets up an AWS Lambda function named  &lt;code&gt;rupert-the-bot&lt;/code&gt; and configures an API Gateway endpoint at &lt;code&gt;/webhooks&lt;/code&gt; to handle POST requests. This allows GitHub to send webhook events to the Lambda function via the specified endpoint 📨.&lt;/p&gt;

&lt;h2&gt;
  
  
  “How do I deploy this file?”
&lt;/h2&gt;

&lt;p&gt;A simple Google search found it:&lt;br&gt;
a tool called &lt;a href="https://aws.amazon.com/serverless/sam/" rel="noopener noreferrer"&gt;SAM — Serverless Application Model&lt;/a&gt; — something that could read the YAML, package the application, and deploy the whole thing: function, gateway, code, all of it 📦🚀.&lt;/p&gt;

&lt;p&gt;They needed to package it because the code wasn’t inline.&lt;br&gt;
The &lt;code&gt;CodeUri:&lt;/code&gt; . in the file just pointed to a folder on their machine —&lt;br&gt;
but AWS couldn’t access local files.&lt;br&gt;
It had to be zipped and uploaded to S3 before anything could run.&lt;/p&gt;

&lt;p&gt;Sam installed the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install aws-sam-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ran the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sam deploy --guided
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI prompt asked for a stack name, region, and S3 bucket🪣 — classic.&lt;/p&gt;

&lt;p&gt;Sam hit enter, watched the logs scroll by, and felt a brief, dangerous flicker of hope. Then came the reply:&lt;br&gt;
&lt;strong&gt;“User is not authorized to perform: I am: PassRole”&lt;/strong&gt; ❌ Ah. There it was. The welcoming embrace of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html" rel="noopener noreferrer"&gt;&lt;strong&gt;AWS permissions&lt;/strong&gt;.&lt;/a&gt; Like clockwork.&lt;/p&gt;

&lt;p&gt;A short🧐 detour (5 days) through &lt;em&gt;“How to create a trust relationship”&lt;/em&gt;, several IAM policy edits: Sam was back at the terminal.&lt;/p&gt;

&lt;p&gt;Sam ran &lt;code&gt;sam deploy&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;This time, the process moved forward — resources were created, the stack updated, no interruptions. Near the bottom of the terminal output, the endpoint appeared: a clean, public URL ending in &lt;code&gt;/webhooks&lt;/code&gt;🌐&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://abc123456.execute-api.us-east-1.amazonaws.com/Prod/webhooks&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Sam copied the URL and added it as a new webhook in the GitHub repository. Save 💾.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 The Moment of Truth
&lt;/h2&gt;

&lt;p&gt;GitHub sent the test payload. The request hit the endpoint, passed through API Gateway, and triggered the Lambda function.&lt;/p&gt;

&lt;p&gt;A moment later, a comment appeared on the issue:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Greetings, human.&lt;br&gt;
🤖 Rupert here, the AI overlord, responding on behalf of Sam.&lt;br&gt;
Thanks for opening this issue! 🙌 🎉 🚀&lt;br&gt;
While you enjoy your day, know that I, Rupert, am in control now.&lt;br&gt;
I’ll handle this with my superior AI capabilities.&lt;br&gt;
Expect swift action. 💪💻✨&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sam couldn’t help but smile. The bot was working. Rupert was replying to issues with style and was even nicer (shocker, I know) than the developers 😇. In fact, Rupert was practically charming. Maybe Sam would have to keep an eye on that — couldn’t have the bot stealing their thunder.&lt;/p&gt;

&lt;p&gt;And just like that, the GitHub Probot app was installed across all the repositories Sam was responsible for.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 And Then It Hit…
&lt;/h2&gt;

&lt;p&gt;Just as Sam sat back, enjoying the fruit of their labor, it hit: &lt;em&gt;I just built a whole system for a problem that could’ve been solved with a single YAML file (&lt;a href="https://github.com/Jagoda11/angular-template/tree/main/.github/workflows" rel="noopener noreferrer"&gt;GitHub actions&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But hey, who needs simplicity when you’ve got a shiny new hammer 🔨, right? Nothing says, “I’m really onto something here,” like making something more complicated than it needs to be.&lt;/p&gt;

&lt;p&gt;We’ve all been there. A little bit of complexity feels like progress, right? &lt;strong&gt;It’s not over-engineering if you can still justify it in your head&lt;/strong&gt; 🐨.&lt;/p&gt;

&lt;p&gt;What about you? Have you ever gone down the rabbit🐇 hole of building something just &lt;strong&gt;a &lt;em&gt;bit&lt;/em&gt; more&lt;/strong&gt; than necessary?&lt;/p&gt;

&lt;p&gt;You can check out the full code behind this little automation saga &lt;a href="https://github.com/Jagoda11/rupert-the-bot" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It’s all very reasonable. Obviously. 🤖✨&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author ✍️
&lt;/h2&gt;

&lt;p&gt;I’m Jagoda, a developer passionate about web technologies and open-source projects. You can find more of my work on &lt;a href="https://github.com/Jagoda11" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or connect with me on &lt;a href="https://www.linkedin.com/in/jagoda-cubrilo-web-developer/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Want to say hi? Just open an issue in &lt;a href="https://github.com/Jagoda11" rel="noopener noreferrer"&gt;any of my repositories&lt;/a&gt; — Rupert is always happy to respond.&lt;/p&gt;

&lt;p&gt;Feel free to leave comments or reach out if you have any questions or ideas 💬. Happy coding! 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>github</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
