<?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: Kevin</title>
    <description>The latest articles on DEV Community by Kevin (@mrmemory).</description>
    <link>https://dev.to/mrmemory</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%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg</url>
      <title>DEV Community: Kevin</title>
      <link>https://dev.to/mrmemory</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mrmemory"/>
    <language>en</language>
    <item>
      <title>Designing for KaiOS - Leveraging Kiro to build an app for the next billion mobile users</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Thu, 04 Dec 2025 04:01:47 +0000</pubDate>
      <link>https://dev.to/mrmemory/designing-for-kaios-leveraging-kiro-to-build-an-app-for-the-next-billion-mobile-users-3o69</link>
      <guid>https://dev.to/mrmemory/designing-for-kaios-leveraging-kiro-to-build-an-app-for-the-next-billion-mobile-users-3o69</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The next billion mobile users aren't getting smartphones, they're getting $20 flip phones running &lt;a href="https://www.kaiostech.com/" rel="noopener noreferrer"&gt;KaiOS&lt;/a&gt;. While the tech world obsesses over the latest iPhone, over 200 million people worldwide rely on feature phones with limited capabilities but essential connectivity.&lt;/p&gt;

&lt;p&gt;My challenge: Build &lt;a href="https://www.bluekai.app" rel="noopener noreferrer"&gt;BlueKai&lt;/a&gt;, a fully-featured &lt;a href="https://bsky.app" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt; client for devices with 256MB RAM, &lt;a href="https://developer.kaiostech.com/docs/sfp-2.5/introduction/overview/" rel="noopener noreferrer"&gt;Gecko 48&lt;/a&gt; (Firefox from 2017), and D-pad navigation. No touch screen. No modern JavaScript APIs. Just the basics, and the determination to bring decentralized social networking to users in remote areas with limited data plans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8jc4rnusz78wgvtvz66.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%2Fq8jc4rnusz78wgvtvz66.png" alt="BlueKai running on a feature phone displaying a post from Hourly Cosmos of the Seahorse nebula" width="800" height="999"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the story of how I used &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, an AI-powered IDE, and a spec-driven development approach to ship a production-ready app in days instead of weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The KaiOS Challenge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.kaiostech.com/" rel="noopener noreferrer"&gt;KaiOS&lt;/a&gt; sits in a unique position in the mobile ecosystem. It powers affordable feature phones sold primarily in emerging markets across India, Africa, Southeast Asia, and Latin America. For many users, a KaiOS device represents their first (and sometimes only) connection to the internet.&lt;/p&gt;

&lt;p&gt;But building for KaiOS means confronting constraints that modern web developers rarely encounter:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Constraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ancient browser engine&lt;/strong&gt;: &lt;a href="https://developer.kaiostech.com/docs/sfp-2.5/introduction/overview/" rel="noopener noreferrer"&gt;Gecko 48&lt;/a&gt; (equivalent to Firefox from 2017) means no Fetch API, limited ES6 support, no modern CSS features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny bundle budget&lt;/strong&gt;: Target under 200KB gzipped for users on prepaid data plans where every kilobyte costs money&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D-pad only navigation&lt;/strong&gt;: No touch screen. Everything must work with up/down/left/right arrow keys and a select button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource constraints&lt;/strong&gt;: 256MB RAM total, shared between the OS and all running apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small displays&lt;/strong&gt;: &lt;a href="https://developer.kaiostech.com/docs/design/ui-design-guideline/" rel="noopener noreferrer"&gt;240x320 pixel screens&lt;/a&gt; in portrait or 320x240 in landscape&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global audience&lt;/strong&gt;: Users speaking dozens of languages, including right-to-left scripts like Arabic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intermittent connectivity&lt;/strong&gt;: 2G/3G networks with frequent drops and high latency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Platform Reality
&lt;/h3&gt;

&lt;p&gt;Unlike iOS or Android where you can find tutorials, Stack Overflow answers, and active communities, KaiOS development is niche. Documentation is sparse. The community is small but passionate (shoutout to &lt;a href="https://wiki.bananahackers.net/" rel="noopener noreferrer"&gt;BananaHackers&lt;/a&gt;). Most developers build once and move on.&lt;/p&gt;

&lt;p&gt;This made it the perfect test case for AI-assisted development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Kiro: AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;Traditional development for a platform like KaiOS is slow. You're constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Researching obscure browser limitations&lt;/li&gt;
&lt;li&gt;Finding workarounds for missing APIs&lt;/li&gt;
&lt;li&gt;Testing on actual hardware (emulators are unreliable)&lt;/li&gt;
&lt;li&gt;Debugging issues that modern DevTools don't handle well&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;'s conversational approach proved invaluable. Instead of searching documentation or Stack Overflow, I could describe behaviors in natural language and get immediate, contextually-aware solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; Early in development, I noticed strange scrolling behavior. I told Kiro: "The cursor can start on top of the action bar and prevent scrolling." Within seconds, Kiro identified the z-index issue and provided a fix that accounted for KaiOS's specific rendering quirks.&lt;/p&gt;

&lt;p&gt;But Kiro's real power wasn't just answering questions. It was helping me think through the entire architecture before writing a single line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spec-Driven Approach
&lt;/h2&gt;

&lt;p&gt;I adopted a three-phase workflow that kept me from drowning in complexity:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Requirements
&lt;/h3&gt;

&lt;p&gt;Before touching any code, I worked with Kiro to define comprehensive user stories using the EARS (Easy Approach to Requirements Syntax) format:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WHEN the user logs in THEN the system SHALL authenticate using BlueSky ATP protocol&lt;/li&gt;
&lt;li&gt;WHEN authenticated THEN the system SHALL display the user's home timeline&lt;/li&gt;
&lt;li&gt;WHEN viewing the timeline THEN the system SHALL show posts with author, content, and timestamp&lt;/li&gt;
&lt;li&gt;WHEN the user selects a post THEN the system SHALL allow liking, reposting, and replying&lt;/li&gt;
&lt;li&gt;WHEN the user navigates to compose THEN the system SHALL allow creating new posts up to 300 characters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This forced me to think through edge cases upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when the session expires mid-scroll?&lt;/li&gt;
&lt;li&gt;How do we handle authentication on slow 2G networks?&lt;/li&gt;
&lt;li&gt;What if the user closes the app mid-login?&lt;/li&gt;
&lt;li&gt;What limitations come from the ancient browser engine?&lt;/li&gt;
&lt;li&gt;What's a typical screen resolution?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2: Design
&lt;/h3&gt;

&lt;p&gt;With requirements locked in, Kiro researched both &lt;a href="https://docs.bsky.app/docs/api/" rel="noopener noreferrer"&gt;BlueSky's AT Protocol API&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/48" rel="noopener noreferrer"&gt;Gecko 48's limitations&lt;/a&gt;. It generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component hierarchies&lt;/strong&gt; showing how UI elements related to each other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data models&lt;/strong&gt; defining the shape of BlueSky posts, profiles, and threads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared reference document&lt;/strong&gt; that became the single source of truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn't boilerplate. It was thoughtful architecture that accounted for KaiOS's constraints. For example, the state management design used an observable pattern compatible with Gecko 48 rather than modern tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Tasks
&lt;/h3&gt;

&lt;p&gt;Kiro broke the implementation into 25 discrete tasks with sub-tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set up Preact&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure build for ES5 output (Gecko 48)&lt;/li&gt;
&lt;li&gt;Set up development server&lt;/li&gt;
&lt;li&gt;Add bundle size analysis&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement XMLHttpRequest wrapper&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Fetch-like API using XHR&lt;/li&gt;
&lt;li&gt;Add timeout handling&lt;/li&gt;
&lt;li&gt;Implement request cancellation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build authentication flow&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create login form with D-pad navigation&lt;/li&gt;
&lt;li&gt;Integrate AT Protocol session management&lt;/li&gt;
&lt;li&gt;Implement session persistence&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each task referenced specific requirements and could be executed incrementally. I could work on one task, review it, commit, and move to the next while maintaining momentum without losing context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Impressive Generation: Internationalization
&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%2F9a91fhxr4uz8fi3eyh9e.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%2F9a91fhxr4uz8fi3eyh9e.png" alt="BlueKai settings menu in French" width="659" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The internationalization system showcased Kiro's ability to handle complex, multi-faceted problems.&lt;/p&gt;

&lt;p&gt;I simply asked: "We need to support multiple languages for a global KaiOS audience."&lt;/p&gt;

&lt;p&gt;Kiro didn't just generate a basic i18n setup. It created a comprehensive system:&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Language Detection
&lt;/h3&gt;

&lt;p&gt;The system detects the device language from KaiOS system settings and extracts the language code (for example, 'en-US' becomes 'en').&lt;/p&gt;

&lt;h3&gt;
  
  
  Eight Languages with Real Translations
&lt;/h3&gt;

&lt;p&gt;Not placeholders but actual translations for English, Spanish, French, Portuguese, Arabic, Hindi, Swahili, and Indonesian. Kiro researched appropriate terminology for each language, including technical terms like "timeline" and "repost."&lt;/p&gt;

&lt;h3&gt;
  
  
  RTL Support
&lt;/h3&gt;

&lt;p&gt;The CSS automatically applies right-to-left (RTL) text direction and reversed flex layouts when Arabic is selected, ensuring proper display for RTL languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful Fallbacks
&lt;/h3&gt;

&lt;p&gt;The translation system checks the current language first, falls back to English if the key isn't found, and finally displays the key itself if no translation exists. This ensures the app never shows blank text.&lt;/p&gt;

&lt;p&gt;This level of thoughtfulness (supporting RTL, providing real translations) would have taken days of research and implementation. Kiro delivered it in one session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hybrid Development Strategy
&lt;/h2&gt;

&lt;p&gt;I discovered that different types of work benefit from different approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Specs for Foundational Features
&lt;/h3&gt;

&lt;p&gt;Complex, multi-component features like architecture, state management, and the &lt;a href="https://docs.bsky.app/docs/api/" rel="noopener noreferrer"&gt;AT Protocol API client&lt;/a&gt; benefited from the full spec-driven approach. These needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-session context preservation&lt;/li&gt;
&lt;li&gt;Systematic thinking about edge cases&lt;/li&gt;
&lt;li&gt;Comprehensive documentation for future maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Vibe Coding for Iterations
&lt;/h3&gt;

&lt;p&gt;Once the foundation was solid, I switched to conversational "vibe coding" for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug fixes ("The menu doesn't close when pressing Back")&lt;/li&gt;
&lt;li&gt;UI tweaks ("Make the timestamp smaller and gray")&lt;/li&gt;
&lt;li&gt;Performance optimization ("Can we lazy-load images in the timeline?")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hybrid approach maximized both thoroughness and speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Wins
&lt;/h2&gt;

&lt;p&gt;Some solutions I'm particularly proud of:&lt;/p&gt;

&lt;h3&gt;
  
  
  XMLHttpRequest Wrapper
&lt;/h3&gt;

&lt;p&gt;Since &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility" rel="noopener noreferrer"&gt;Gecko 48 lacks the Fetch API&lt;/a&gt;, I needed to wrap XMLHttpRequest in a Promise-based interface that provides modern async/await functionality while working within the browser's limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual Scrolling
&lt;/h3&gt;

&lt;p&gt;With limited memory, rendering hundreds of posts would crash the app. Virtual scrolling renders only visible items plus a small buffer above and below the viewport, dramatically reducing memory usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  D-Pad Navigation Manager
&lt;/h3&gt;

&lt;p&gt;KaiOS navigation requires careful focus management. The navigation system listens for arrow key events and maintains a list of focusable elements, moving focus appropriately and triggering selection on Enter key press.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://developer.kaiostech.com/docs/design/keypad-navigation/" rel="noopener noreferrer"&gt;KaiOS D-pad navigation guidelines&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aggressive Caching
&lt;/h3&gt;

&lt;p&gt;Users on prepaid plans can't afford to re-download content. Service Workers cache timeline posts, profiles, and media, responding with cached versions when available and only fetching from the network when necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundle Optimization
&lt;/h3&gt;

&lt;p&gt;Final bundle: &lt;strong&gt;73.45KB gzipped&lt;/strong&gt;, well under the 200KB target. Achieved through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tree-shaking unused &lt;a href="https://preactjs.com/" rel="noopener noreferrer"&gt;Preact&lt;/a&gt; features&lt;/li&gt;
&lt;li&gt;Dynamic imports for routes&lt;/li&gt;
&lt;li&gt;Optimized image loading&lt;/li&gt;
&lt;li&gt;Minification and compression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Learn more about &lt;a href="https://developer.kaiostech.com/docs/best-practice/performance/" rel="noopener noreferrer"&gt;optimizing apps for KaiOS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact
&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%2Faxrnftpdc99lbm3pmbou.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%2Faxrnftpdc99lbm3pmbou.png" alt="Example timeline view" width="478" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BlueKai (&lt;a href="https://bluekai.app" rel="noopener noreferrer"&gt;bluekai.app&lt;/a&gt;) represents more than a technical achievement. It's about:&lt;/p&gt;

&lt;h3&gt;
  
  
  Bringing Modern Social Networking to New Markets
&lt;/h3&gt;

&lt;p&gt;BlueSky's decentralized, ad-free approach is perfect for users on limited data plans. BlueKai makes it accessible to people who couldn't otherwise participate in the &lt;a href="https://atproto.com/" rel="noopener noreferrer"&gt;AT Protocol network&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proving AI Can Tackle Niche Platforms
&lt;/h3&gt;

&lt;p&gt;Most AI coding tools optimize for mainstream platforms (React, Next.js, iOS, Android). BlueKai shows that with the right approach, AI can handle obscure platforms with sparse documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Future of Global-First Development
&lt;/h3&gt;

&lt;p&gt;If a solo developer can build a production-ready app for a niche platform in days, what becomes possible for small teams targeting underserved markets?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI tools like &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; make it feasible for small teams (or solo developers) to build for platforms that otherwise would have a steep learning curve with many paper cuts. The combination of spec-driven architecture and conversational iteration proved powerful: thoughtful design meets rapid execution.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;BlueKai is open source and available at &lt;a href="https://bluekai.app" rel="noopener noreferrer"&gt;bluekai.app&lt;/a&gt;. Try it on any KaiOS device or in the &lt;a href="https://developer.kaiostech.com/docs/sfp-2.5/getting-started/env-setup/simulator/" rel="noopener noreferrer"&gt;KaiOS simulator&lt;/a&gt;. Built for &lt;a href="https://kiroween.devpost.com/" rel="noopener noreferrer"&gt;Kiroween 2025&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Special thanks to the &lt;a href="https://wiki.bananahackers.net/" rel="noopener noreferrer"&gt;BananaHackers community&lt;/a&gt; and the KaiOS team for their documentation and tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>kiro</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Weekend Project- Building a Serverless Phishing Detector for Google's Cloud Run Hackathon</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Mon, 10 Nov 2025 04:04:47 +0000</pubDate>
      <link>https://dev.to/mrmemory/weekend-project-building-a-serverless-phishing-detector-for-googles-cloud-run-hackathon-3b9b</link>
      <guid>https://dev.to/mrmemory/weekend-project-building-a-serverless-phishing-detector-for-googles-cloud-run-hackathon-3b9b</guid>
      <description>&lt;p&gt;&lt;em&gt;I created this blog post for the purposes of entering the &lt;a href="https://run.devpost.com/" rel="noopener noreferrer"&gt;Google Cloud Run hackathon&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I set out to build &lt;a href="https://github.com/kevinl95/ParsePhish" rel="noopener noreferrer"&gt;ParsePhish&lt;/a&gt; for the Google Cloud Run hackathon, I thought I had a solid plan: create a dual-purpose API that could analyze both emails AND URLs for phishing indicators. What I ended up with was a much more focused, production-ready solution - and a valuable lesson about the power of simplicity in AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what is ParsePhish?
&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%2Fy0x3s8fz0r754b9bs5zv.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%2Fy0x3s8fz0r754b9bs5zv.png" alt="Architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ParsePhish is a REST API that uses transformer embeddings and GPU-accelerated similarity search to analyze email content for phishing indicators. It runs entirely serverless on &lt;a href="https://cloud.google.com/run?hl=en" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; with NVIDIA L4 GPUs. It was designed to be privacy-respecting and inexpensive to run. My idea was that now you could detect phishing messages without needing to send your messages to a service that you don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Original Vision (And Its Problems)
&lt;/h2&gt;

&lt;p&gt;My initial idea was ambitious: why not detect phishing in both email content AND suspicious URLs? I built a FastAPI service with two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/analyze/email&lt;/code&gt; - for email content analysis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/analyze/url&lt;/code&gt; - for URL analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The email analysis worked beautifully. Using &lt;a href="https://www.aimodels.fyi/models/huggingFace/e5-small-v2-intfloat" rel="noopener noreferrer"&gt;SentenceTransformers&lt;/a&gt; and &lt;a href="https://github.com/facebookresearch/faiss" rel="noopener noreferrer"&gt;FAISS (Facebook AI Similarity Search)&lt;/a&gt; similarity search on the cloud GPUs, it could accurately identify phishing patterns in email text. But the URL analysis? That's where things got interesting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nv"&gt;$API_URL&lt;/span&gt;/analyze/url &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://google.com"}'&lt;/span&gt;
&lt;span class="c"&gt;# Response: {"phishy_score": 0.8, "verdict": "Potential phishing"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Google.com with a phishing score of 0.8?&lt;/strong&gt; Something was very wrong.&lt;/p&gt;

&lt;p&gt;The problem wasn't with the AI model - it was with my approach. The URL analysis was fundamentally flawed and destined to not be useful because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Context Loss: A URL without user context is just a string - the real phishing happens in how it's presented&lt;/li&gt;
&lt;li&gt;Domain Complexity: Modern web infrastructure (CDNs, subdomains, redirects) broke simple pattern matching and would make sure that even if I analyzed a URL in a sketchy email it would likely not do the user any good.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  What I actually built
&lt;/h2&gt;

&lt;p&gt;What remained after I stripped out the URL analysis feature was a laser-focused email phishing detection API that works reliably.&lt;/p&gt;

&lt;p&gt;Here's the breakdown:&lt;/p&gt;

&lt;p&gt;Architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI backend deployed on Cloud Run with NVIDIA L4 GPUs&lt;/li&gt;
&lt;li&gt;SentenceTransformers (&lt;code&gt;intfloat/e5-small-v2&lt;/code&gt;) for GPU-accelerated 384-dimensional text embeddings&lt;/li&gt;
&lt;li&gt;FAISS CPU for high-speed similarity search against known phishing patterns&lt;/li&gt;
&lt;li&gt;Python container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI Pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text normalization and preprocessing&lt;/li&gt;
&lt;li&gt;GPU-accelerated transformer embedding generation&lt;/li&gt;
&lt;li&gt;FAISS similarity search against training corpus&lt;/li&gt;
&lt;li&gt;Intelligent scoring combining similarity votes with pattern matching&lt;/li&gt;
&lt;li&gt;Real-time response with explanatory details&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cloud Run Configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;16Gi memory + 4 vCPUs for GPU instances&lt;/li&gt;
&lt;li&gt;Scale-to-zero for cost efficiency&lt;/li&gt;
&lt;li&gt;HTTPS-only with automatic TLS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it out here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kevinl95" rel="noopener noreferrer"&gt;
        kevinl95
      &lt;/a&gt; / &lt;a href="https://github.com/kevinl95/ParsePhish" rel="noopener noreferrer"&gt;
        ParsePhish
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ParsePhish uses NVIDIA L4 GPUs on Google Cloud Run to detect email phishing entirely serverless! Transformer embeddings + FAISS similarity search = real-time protection without storing your data.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/kevinl95/ParsePhish/assets/logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fkevinl95%2FParsePhish%2Fassets%2Flogo.png" alt="The text ParsePhish displayed underneath a shield with a fish on it." width="200"&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ParsePhish Email Analysis API&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GPU-powered phishing detection for email content&lt;/strong&gt;&lt;br&gt;
Serverless AI-powered email analysis using transformer embeddings and similarity search.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ParsePhish is a REST API that uses transformer embeddings and GPU-accelerated similarity search to analyze email content for phishing indicators. Built for the &lt;strong&gt;Cloud Run GPU Category&lt;/strong&gt; hackathon, it runs entirely serverless on &lt;strong&gt;Google Cloud Run with NVIDIA L4 GPUs&lt;/strong&gt;.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Before deploying, you'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud CLI&lt;/strong&gt;: &lt;a href="https://cloud.google.com/sdk/docs/install" rel="nofollow noopener noreferrer"&gt;Install gcloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Project&lt;/strong&gt;: Create a project with billing enabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: Run &lt;code&gt;gcloud auth login&lt;/code&gt; and &lt;code&gt;gcloud auth application-default login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU Quota&lt;/strong&gt;: Request NVIDIA L4 GPU quota in europe-west4 region&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required APIs&lt;/strong&gt;: The deployment script will enable these automatically:
&lt;ul&gt;
&lt;li&gt;Cloud Run API&lt;/li&gt;
&lt;li&gt;Cloud Build API&lt;/li&gt;
&lt;li&gt;Container Registry API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Deploy to Cloud Run&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Clone the repository&lt;/span&gt;
git clone https://github.com/kevinl95/ParsePhish.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; ParsePhish
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Deploy with GPU support to your Google Cloud project&lt;/span&gt;
./deploy.sh YOUR_PROJECT_ID&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kevinl95/ParsePhish" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Once you deploy it, give it a shot:&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;# Obvious phishing email&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nv"&gt;$API_URL&lt;/span&gt;/analyze/email &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "URGENT! Click here or your account will be deleted!",
    "subject": "Security Alert"
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Legitimate email  &lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nv"&gt;$API_URL&lt;/span&gt;/analyze/email &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "Your monthly statement is available in your account portal.",
    "subject": "Statement Ready"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where of course &lt;code&gt;API_URL&lt;/code&gt; is your deployed ParsePhish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where next?
&lt;/h2&gt;

&lt;p&gt;I would love to prototype extensions for Thunderbird and other email clients so people can take advantage of this tool right from their favorite email apps. For now, I'm excited to get this out in front of the community to see if it can be valuable for anyone building tools that can make use of an easy-to-deploy phishing detector.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>python</category>
      <category>api</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Check out my new GitLab CI/CD module- it provides certainty that when you deploy your new web feature it will work cross-browser and cause you minimal headaches!</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sun, 12 Oct 2025 17:36:56 +0000</pubDate>
      <link>https://dev.to/mrmemory/-32hi</link>
      <guid>https://dev.to/mrmemory/-32hi</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/mrmemory" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" alt="mrmemory"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/mrmemory/web-check-ci-catch-browser-compatibility-issues-before-they-break-production-273" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Web Check CI: Catch Browser Compatibility Issues Before They Break Production&lt;/h2&gt;
      &lt;h3&gt;Kevin ・ Oct 11&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#hacktoberfest&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#opensource&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Web Check CI: Catch Browser Compatibility Issues Before They Break Production</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sat, 11 Oct 2025 22:15:22 +0000</pubDate>
      <link>https://dev.to/mrmemory/web-check-ci-catch-browser-compatibility-issues-before-they-break-production-273</link>
      <guid>https://dev.to/mrmemory/web-check-ci-catch-browser-compatibility-issues-before-they-break-production-273</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest2025"&gt;2025 Hacktoberfest Writing Challenge&lt;/a&gt;: Maintainer Spotlight&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started It All
&lt;/h2&gt;

&lt;p&gt;Picture this: You've just deployed a beautiful new feature, but within hours, support tickets start flooding in. Safari users can't see your popover menus. Older Chrome versions throw JavaScript errors. Your team spends the next day debugging compatibility issues that could have been caught before deployment.&lt;/p&gt;

&lt;p&gt;This exact scenario inspired me to build &lt;a href="https://gitlab.com/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;Web Check CI&lt;/a&gt; - an automated browser compatibility scanner that integrates directly into &lt;a href="https://docs.gitlab.com/ci/" rel="noopener noreferrer"&gt;GitLab CI/CD pipelines&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Web Check CI Special
&lt;/h2&gt;

&lt;p&gt;Web Check CI stands out because it is the &lt;a href="https://gitlab.com/explore/catalog/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;first CI/CD module of its kind&lt;/a&gt; available for &lt;a href="https://about.gitlab.com/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;! It's built on &lt;a href="https://web.dev/baseline/" rel="noopener noreferrer"&gt;Google's Baseline&lt;/a&gt; initiative, the new standard for web platform compatibility. Instead of guessing which features are safe to use, developers get authoritative answers based on real browser support data.&lt;/p&gt;

&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-line setup in GitLab CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Scans JavaScript, CSS, and HTML for compatibility issues&lt;/li&gt;
&lt;li&gt;Configurable baseline years (2023, 2024, 2025)&lt;/li&gt;
&lt;li&gt;Smart feature detection using regex patterns, not just keyword matching&lt;/li&gt;
&lt;li&gt;Inline merge request comments showing exactly where issues exist&lt;/li&gt;
&lt;li&gt;Allow/deny lists for team-specific rules&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%2Fv01yhct9q1mnu51myzxs.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%2Fv01yhct9q1mnu51myzxs.png" alt="Example of Web Check CI running on a real GitLab repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of Web Check CI running on a real GitLab repo&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&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%2F63srgoovnj0g4hgt82ym.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%2F63srgoovnj0g4hgt82ym.png" alt="Diagram of how Web Check CI works generated using Mermaid and Eraser.io"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Web Check is both a CI component for GitLab and a powerful CLI tool. The architecture combines three powerful Node.js libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/commander" rel="noopener noreferrer"&gt;Commander.js&lt;/a&gt; - Clean CLI interface with automatic validation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/glob" rel="noopener noreferrer"&gt;Glob&lt;/a&gt; - Smart file discovery that skips node_modules and test files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/web-features" rel="noopener noreferrer"&gt;Web-Features&lt;/a&gt; - Google's comprehensive database of 1000+ web platform features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scanning process is sophisticated yet fast. It checks every web feature against actual code usage, not just mentions in comments or documentation.&lt;/p&gt;

&lt;p&gt;Here's some sample output you can expect from WebCheck CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❌ Found 2 baseline issues:
🚫 Explicitly Denied Features:
  app.js:11 - document-write
    document.write&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;p&amp;gt;This should be caught&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    → Explicitly denied

⚠️  Baseline Compatibility Issues:
  app.js:14 - popover
    element.popover &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'auto'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    → Not &lt;span class="k"&gt;in &lt;/span&gt;baseline 2024 &lt;span class="o"&gt;(&lt;/span&gt;available from 2025&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Real-World Impact
&lt;/h2&gt;

&lt;p&gt;Since launching this month, Web Check CI has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@kevinloeffler/web-check" rel="noopener noreferrer"&gt;500+ weekly downloads from NPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/explore/catalog/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;Published to GitLab Component Catalog for seamless integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Achieved high test coverage&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why I'm Passionate About This Project
&lt;/h2&gt;

&lt;p&gt;Web compatibility shouldn't be an afterthought. Every developer has experienced the frustration of browser compatibility issues breaking user experiences. Web Check CI transforms this reactive debugging into proactive prevention. Not every developer has time to track which features work where. This tool brings Google's authoritative compatibility data directly into everyday workflows.&lt;/p&gt;
&lt;h2&gt;
  
  
  How You Can Contribute
&lt;/h2&gt;

&lt;p&gt;Web Check CI is actively seeking contributors! Here are some ways to get involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try it out: Run &lt;code&gt;npx @kevinloeffler/web-check&lt;/code&gt; on your projects&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/explore/catalog/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;Add it to your GitLab CI:&lt;/a&gt; Use our component for automated scanning. You can add it with just a few lines!&lt;/li&gt;
&lt;li&gt;Report issues: Found a bug or false positive? Open an issue!&lt;/li&gt;
&lt;li&gt;Improve feature detection: Better regex patterns for CSS/JS features&lt;/li&gt;
&lt;li&gt;Platform integrations: &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;, for example&lt;/li&gt;
&lt;li&gt;Documentation: Examples, tutorials, use case guides&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;NPM Package: &lt;code&gt;@kevinloeffler/web-check&lt;/code&gt; or &lt;a href="https://www.npmjs.com/package/@kevinloeffler/web-check" rel="noopener noreferrer"&gt;click here&lt;/a&gt;&lt;br&gt;
GitLab Component: &lt;code&gt;loefflerlabs/web-check-ci/templates/web-check.yml@v1.0.8&lt;/code&gt; or &lt;a href="https://gitlab.com/explore/catalog/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;click here&lt;/a&gt;&lt;br&gt;
Repository:&lt;br&gt;


&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://gitlab.com/loefflerlabs/web-check-ci" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgitlab.com%2Fuploads%2F-%2Fsystem%2Fproject%2Favatar%2F74530549%2FWeb_Check_Logo.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://gitlab.com/loefflerlabs/web-check-ci" rel="noopener noreferrer" class="c-link"&gt;
            Kevin Loeffler / Web Check CI · GitLab
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Web Check CI automatically enforces Google's Baseline web compatibility standards in your GitLab pipelines. It scans your JavaScript, CSS, and HTML for unsupported features and reports issues directly...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgitlab.com%2Fassets%2Ffavicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png"&gt;
          gitlab.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Basic GitLab Integration in your GitLab CI YAML file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lint&lt;/span&gt;

&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;loefflerlabs/web-check-ci'&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1.0.8'&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;templates/web-check.yml'&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BASELINE_YEAR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2024&lt;/span&gt;
  &lt;span class="na"&gt;SEVERITY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;major&lt;/span&gt;
  &lt;span class="na"&gt;DENY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document-write"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I've Learned as a Maintainer
&lt;/h2&gt;

&lt;p&gt;Building Web Check CI taught me that developer experience is everything. The tool needed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero-configuration for basic use cases&lt;/li&gt;
&lt;li&gt;Highly configurable for advanced needs&lt;/li&gt;
&lt;li&gt;Fast and reliable for CI/CD environments&lt;/li&gt;
&lt;li&gt;Clear and actionable in error reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most rewarding part? Seeing developers discover compatibility issues they never knew existed in their codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join Our Community
&lt;/h2&gt;

&lt;p&gt;You can find the &lt;a href="https://gitlab.com/loefflerlabs/web-check-ci" rel="noopener noreferrer"&gt;repo&lt;/a&gt; and &lt;a href="https://gitlab.com/loefflerlabs/web-check-ci/-/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contribution guide&lt;/a&gt; on GitLab!&lt;/p&gt;

&lt;p&gt;Let's build a more compatible web together.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fun project I put together this week in response to the rise of Quishing, or QR-code phishing. QRTrust is a free progressive web app that scans QR codes and checks the URLs they encode against the PhishTank database.</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sat, 12 Jul 2025 01:08:43 +0000</pubDate>
      <link>https://dev.to/mrmemory/fun-project-i-put-together-this-week-in-response-to-the-rise-of-quishing-or-qr-code-phishing-5add</link>
      <guid>https://dev.to/mrmemory/fun-project-i-put-together-this-week-in-response-to-the-rise-of-quishing-or-qr-code-phishing-5add</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m" class="crayons-story__hidden-navigation-link"&gt;QRTrust: Privacy-First QR Scanner with Phishing Detection&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mrmemory" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" alt="mrmemory profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mrmemory" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Kevin
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Kevin
                
              
              &lt;div id="story-author-preview-content-2679338" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mrmemory" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Kevin&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 12 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m" id="article-link-2679338"&gt;
          QRTrust: Privacy-First QR Scanner with Phishing Detection
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cybersecurity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cybersecurity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vite"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vite&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>cybersecurity</category>
      <category>vite</category>
    </item>
    <item>
      <title>QRTrust: Privacy-First QR Scanner with Phishing Detection</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sat, 12 Jul 2025 01:04:05 +0000</pubDate>
      <link>https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m</link>
      <guid>https://dev.to/mrmemory/qrtrust-privacy-first-qr-scanner-with-phishing-detection-94m</guid>
      <description>&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%2F0b6yqsnfc79fbhx36py4.gif" 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%2F0b6yqsnfc79fbhx36py4.gif" alt="Text reading "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/QR_code" rel="noopener noreferrer"&gt;QR codes&lt;/a&gt; are everywhere, including restaurants, events, payments, and marketing materials. They have become endemic since the end of the COVID lockdowns. But have you ever wondered where that QR code is actually taking you before scanning it? That's the problem I set out to solve with &lt;a href="https://www.qrtrust.fyi" rel="noopener noreferrer"&gt;&lt;strong&gt;QRTrust&lt;/strong&gt;&lt;/a&gt;, a privacy-focused Progressive Web App that scans QR codes and checks URLs for phishing threats before you visit them!&lt;/p&gt;

&lt;h2&gt;
  
  
  Quishing
&lt;/h2&gt;

&lt;p&gt;Many of us have heard about phishing, where websites and emails pretend are created that pretend to be a real service or person. Quishing is similar, except with QR codes &lt;a href="https://www.9news.com/article/money/consumer/steve-on-your-side/cherry-creek-quishing-scam-stickers-parking-signs/73-d537d8ab-392d-46d9-959d-8854267a5217" rel="noopener noreferrer"&gt;and it's a growing problem worldwide, including where I live&lt;/a&gt;. People post QR codes pretending to be a service letting you pay for parking, order food, pay for fuel, but instead they are collecting your information for scams and identity theft.&lt;/p&gt;

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

&lt;p&gt;QRTrust provides a simple, privacy-first solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan QR codes&lt;/strong&gt; using your device's camera&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check URLs&lt;/strong&gt; against &lt;a href="https://phishtank.org/" rel="noopener noreferrer"&gt;PhishTank&lt;/a&gt;'s community-driven phishing database. QRTrust does not log addresses from users, and it is fully open source so you can verify! Plus, if you want, the whole thing can deploy to &lt;a href="//www.netlify.com"&gt;Netlify&lt;/a&gt; with a few commands if you want a private instance- check out the link to GitHub at the bottom of this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get clear feedback&lt;/strong&gt;: Safe ✅, Suspicious ⚠️, or Unknown ❓&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make informed decisions&lt;/strong&gt; with detailed warnings and safe browsing options. QRTrust does not immediately navigate to the linked website- it lets you choose. You'll get presented with the full, human readable URL as well as what we found on PhishTank.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's also a &lt;a href="https://web.dev/learn/pwa/progressive-web-apps/" rel="noopener noreferrer"&gt;progressive web app&lt;/a&gt;, which means you can install it on any device you please- no app store required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it live&lt;/strong&gt;: &lt;a href="https://qrtrust.fyi" rel="noopener noreferrer"&gt;qrtrust.fyi&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture &amp;amp; Technology Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frontend Technologies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSS3 with Custom Properties&lt;/strong&gt; - Modern styling with gradients and animations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML5&lt;/strong&gt; - Semantic markup and PWA manifest&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vite.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Vite&lt;/strong&gt;&lt;/a&gt; - Fast build tool and development server&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Core Libraries
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@zxing/browser" rel="noopener noreferrer"&gt;@zxing/browser&lt;/a&gt;&lt;/strong&gt; - QR code scanning using device camera&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend &amp;amp; API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.netlify.com/platform/core/functions/" rel="noopener noreferrer"&gt;&lt;strong&gt;Netlify Functions&lt;/strong&gt;&lt;/a&gt; - Serverless functions for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; proxy. This is necessary because PhishTank does not allow websites to make client-side requests. This made it simple to check URLs without worrying much about the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PhishTank API&lt;/strong&gt; - Community-driven phishing URL database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Progressive Web App Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web App Manifest&lt;/strong&gt; - Native app-like installation. Launch it right from your homescreen, and I don't have to worry about submitting to app stores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt; - Works on Android and iOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;Check out the code on GitHub!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kevinl95" rel="noopener noreferrer"&gt;
        kevinl95
      &lt;/a&gt; / &lt;a href="https://github.com/kevinl95/qrtrust" rel="noopener noreferrer"&gt;
        qrtrust
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Privacy-focused PWA for checking if QR codes lead to known phishing sites
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;QRTrust&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://app.netlify.com/projects/qrtrust/deploys" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/88326560c56ae6c52545beb9b1d7ad8c46aeee4e645403cf37651d160f66c6e4/68747470733a2f2f6170692e6e65746c6966792e636f6d2f6170692f76312f6261646765732f62366535343335642d323361342d343064382d383339372d3137663630623830336463382f6465706c6f792d737461747573" alt="Netlify Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A privacy-focused Progressive Web App (PWA) that scans QR codes and checks if they lead to known phishing sites before you visit them.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time QR Code Scanning&lt;/strong&gt; - Uses your device's camera to scan QR codes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phishing Detection&lt;/strong&gt; - Checks URLs against PhishTank's database of known phishing sites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-First&lt;/strong&gt; - No URLs or personal data are logged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive Web App&lt;/strong&gt; - Install on your device like a native app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile-Optimized&lt;/strong&gt; - Responsive design for all devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security-Focused&lt;/strong&gt; - Clear warnings and safe browsing recommendations&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🔍 How It Works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan QR Code&lt;/strong&gt; - Point your camera at any QR code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Analysis&lt;/strong&gt; - The app extracts the URL and checks it against &lt;a href="https://phishtank.org" rel="nofollow noopener noreferrer"&gt;PhishTank's database&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety Assessment&lt;/strong&gt; - Get instant feedback
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Safe&lt;/strong&gt; - URL appears clean, safe to visit&lt;/li&gt;
&lt;li&gt;
⚠️ &lt;strong&gt;Suspicious&lt;/strong&gt; - URL found in phishing database&lt;/li&gt;
&lt;li&gt;❓ &lt;strong&gt;Unknown&lt;/strong&gt; - Unable to verify (service unavailable)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Informed Decision&lt;/strong&gt; -…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kevinl95/qrtrust" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>cybersecurity</category>
      <category>vite</category>
    </item>
    <item>
      <title>I had a lot of fun over the long weekend putting this together! I think it can be a valuable tool for teachers, freelancers, and anyone who routinely receives email attachments they could better manage from their Google Drive.</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 27 May 2025 03:02:52 +0000</pubDate>
      <link>https://dev.to/mrmemory/i-had-a-lot-of-fun-over-the-long-weekend-putting-this-together-i-think-it-can-be-a-valuable-tool-1nh9</link>
      <guid>https://dev.to/mrmemory/i-had-a-lot-of-fun-over-the-long-weekend-putting-this-together-i-think-it-can-be-a-valuable-tool-1nh9</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj" class="crayons-story__hidden-navigation-link"&gt;Drop It Like It’s Hot: Sending Email Attachments Straight to Google Drive using Postmark&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mrmemory" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" alt="mrmemory profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mrmemory" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Kevin
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Kevin
                
              
              &lt;div id="story-author-preview-content-2525495" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mrmemory" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Kevin&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 27 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj" id="article-link-2525495"&gt;
          Drop It Like It’s Hot: Sending Email Attachments Straight to Google Drive using Postmark
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postmarkchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postmarkchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;10&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              3&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Drop It Like It’s Hot: Sending Email Attachments Straight to Google Drive using Postmark</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 27 May 2025 02:16:34 +0000</pubDate>
      <link>https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj</link>
      <guid>https://dev.to/mrmemory/drop-it-like-its-hot-sending-email-attachments-straight-to-google-drive-using-postmark-8hj</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Teachers, freelancers, and &lt;a href="https://www.howtogeek.com/413507/what-is-inbox-zero-and-how-can-you-achieve-it/" rel="noopener noreferrer"&gt;inbox zero&lt;/a&gt; purists rejoice: I built &lt;a href="https://github.com/kevinl95/EmailDrop" rel="noopener noreferrer"&gt;EmailDrop&lt;/a&gt;, a one-click &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; deployment that turns incoming emails into automatic &lt;a href="https://workspace.google.com/products/drive/" rel="noopener noreferrer"&gt;Google Drive&lt;/a&gt; uploads. With &lt;a href="https://postmarkapp.com/blog/an-introduction-to-inbound-email-parsing-what-it-is-and-how-you-can-do-it" rel="noopener noreferrer"&gt;Postmark's new inbound webhooks&lt;/a&gt;, &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt;, and a little &lt;a href="https://en.wikipedia.org/wiki/OAuth" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt; wizardry, attachments fly straight from your inbox to your Google Drive. In this post, I’ll walk through how I built it using &lt;a href="//postmarkapp.com"&gt;Postmark&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt;, Google Drive, and &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing" rel="noopener noreferrer"&gt;serverless&lt;/a&gt; tools, and how you can deploy it with zero manual code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use EmailDrop?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4og0qlh8u3tx6x2cbj2.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%2Fh4og0qlh8u3tx6x2cbj2.png" alt="Circular diagram showing emails flowing from the user's email inbox to a Postmark webhook to AWS Lambda which uploads the attached files to Google Drive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploys with a single click, no coding required (see the demo below and deploy in minutes!)&lt;/li&gt;
&lt;li&gt;Have your students submit their homework direct to your Google Drive, and without having to give out your real email address&lt;/li&gt;
&lt;li&gt;Process documents from coworkers, employees, or customers without having to manually download attachments and upload them for editing and storage&lt;/li&gt;
&lt;li&gt;Set up a photo folder for special events like weddings, birthdays, and more!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;EmailDrop is a CloudFormation stack with some Python code that will simplify your email inbox. First, however, we need to gather some information from Google. Then, we'll click a Launch Stack button that will deploy all of the infrastructure- no coding required. Then we'll configure Postmark and wrap up. Let's go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tiosz0ndajug1675qgf.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%2F8tiosz0ndajug1675qgf.png" alt="Creating a Google Cloud project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go to Google Cloud Console&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;https://console.cloud.google.com/&lt;/a&gt;
&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%2F9qm6t45iohuw4y17hfcx.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%2F9qm6t45iohuw4y17hfcx.png" alt="Selecting API &amp;amp; Services"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a Project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the project dropdown → New Project&lt;/li&gt;
&lt;li&gt;Name it (e.g., PostmarkUploader)&lt;/li&gt;
&lt;li&gt;Click Create&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%2F3g3qffx0blyxa45se4v5.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%2F3g3qffx0blyxa45se4v5.png" alt="Enabling the Google Drive API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable the Google Drive API&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services&lt;/strong&gt; → &lt;strong&gt;Library&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Search for Google Drive API&lt;/li&gt;
&lt;li&gt;Click Enable&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%2Fz8rrya83unx6mc47w75w.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%2Fz8rrya83unx6mc47w75w.png" alt="Configuring the OAuth screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the OAuth Consent Screen&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services&lt;/strong&gt; → &lt;strong&gt;OAuth consent screen&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose External&lt;/li&gt;
&lt;li&gt;Fill in:

&lt;ul&gt;
&lt;li&gt;App name&lt;/li&gt;
&lt;li&gt;Support email&lt;/li&gt;
&lt;li&gt;Developer contact email (these can all be your personal email)&lt;/li&gt;
&lt;/ul&gt;


&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%2Fkvt9mn8nq6b2a1t2xeyt.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%2Fkvt9mn8nq6b2a1t2xeyt.png" alt="Making yourself a test user"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add yourself as a test user using the &lt;strong&gt;Audience&lt;/strong&gt; page on the sidebar&lt;/li&gt;
&lt;li&gt;Click Save and Continue&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%2F3wq8xqaxdqtup6c4ir7k.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%2F3wq8xqaxdqtup6c4ir7k.png" alt="Creating OAuth 2.0 credentials"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create OAuth 2.0 Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services&lt;/strong&gt; → &lt;strong&gt;Credentials&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Credentials&lt;/strong&gt; → &lt;strong&gt;OAuth client ID&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose Web application&lt;/li&gt;
&lt;li&gt;Skip the redirect URI for now — you'll add it after deploying the stack&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;li&gt;Save the Client ID and Client Secret&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?templateURL=https://emaildroppostmark.s3.us-west-2.amazonaws.com/cloudformation.yml" rel="noopener noreferrer"&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%2Fr2ghcs7c9kfqqlnbap7j.png" alt="Launch Stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy the CloudFormation Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the "Launch Stack" button above!&lt;/li&gt;
&lt;li&gt;Name the stack anything you wish.&lt;/li&gt;
&lt;li&gt;Fill in the parameters:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GoogleClientId&lt;/strong&gt;: (from step 5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GoogleClientSecret&lt;/strong&gt;: (from step 5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GoogleDriveFolder&lt;/strong&gt;: (optional) Name of the folder to store attachments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LambdaTimeout&lt;/strong&gt;: (optional) Default is 60 seconds. Increase for large attachments (up to 900s)&lt;/li&gt;
&lt;/ul&gt;


&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%2Feb0qgo3h8211pr09rail.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%2Feb0qgo3h8211pr09rail.png" alt="Allowing AWS to create new IAM resources for you"&gt;&lt;/a&gt;&lt;br&gt;
     - Agree to let AWS create new IAM resources and click `Create Stack'. EmailDrop will take several minutes to deploy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hq0m24lwdnkzoro0d5k.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%2F6hq0m24lwdnkzoro0d5k.png" alt="Copying the redirect URI and going through the Auth Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the Redirect URI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once the stack is deployed, go to the Outputs tab in CloudFormation&lt;/li&gt;
&lt;li&gt;Copy the OAuthURL output&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%2F13hjj9jcjanov96c9nh4.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%2F13hjj9jcjanov96c9nh4.png" alt="Adding the redirect URI on Google Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return to Google Cloud Console → Clients&lt;/li&gt;
&lt;li&gt;Click on your OAuth 2.0 client&lt;/li&gt;
&lt;li&gt;Edit the Authorized redirect URIs&lt;/li&gt;
&lt;li&gt;Paste in the URL you copied from the stack output&lt;/li&gt;
&lt;li&gt;Save changes&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%2Febvaoaapmzceflgji9jc.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%2Febvaoaapmzceflgji9jc.png" alt="Authorizing Google Drive access"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Complete the OAuth Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the stack outputs, find the OAuthURL&lt;/li&gt;
&lt;li&gt;Open it in your browser&lt;/li&gt;
&lt;li&gt;Click the link to begin the authentication flow, as shown above.&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%2Ff4nwizcb6oea66nd24g3.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%2Ff4nwizcb6oea66nd24g3.png" alt="Google Unverified App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will get a warning that Google has not verified this application. Click 'Continue'&lt;/li&gt;
&lt;li&gt;Approve access to Google Drive&lt;/li&gt;
&lt;li&gt;The Lambda will exchange the code for tokens and store them&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%2Fhsj6wmrykw6dzrfr5qzt.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%2Fhsj6wmrykw6dzrfr5qzt.png" alt="Successful authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can now close this page, authentication is finished!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create a Postmark Account&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign up at &lt;a href="https://postmarkapp.com" rel="noopener noreferrer"&gt;postmarkapp.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new server (or use an existing one)&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%2F17lebwl5awxubp6zy5hc.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%2F17lebwl5awxubp6zy5hc.png" alt="Setting up Postmark"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set Up Inbound Email Processing&lt;/strong&gt;&lt;br&gt;
    - In your Postmark dashboard, navigate to &lt;strong&gt;Servers&lt;/strong&gt; → &lt;strong&gt;Your Server Name&lt;/strong&gt; → &lt;strong&gt;Default Inbound Stream&lt;/strong&gt;&lt;br&gt;
    - Navigate to &lt;strong&gt;Setup Instructions&lt;/strong&gt;&lt;br&gt;
    - Note your unique inbound email address&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdh3w58lp7rjqwsdmgobj.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%2Fdh3w58lp7rjqwsdmgobj.png" alt="Adding your webhook URL and ensuring Lambda gets the JSON payload from Postmark"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure Webhook URL&lt;/strong&gt;&lt;br&gt;
    - From the &lt;strong&gt;Settings&lt;/strong&gt; page, find the &lt;strong&gt;Webhook URL&lt;/strong&gt; field&lt;br&gt;
    - In the CloudFormation stack outputs, find the &lt;strong&gt;PostmarkWebhookURL&lt;/strong&gt; value&lt;br&gt;
    - Copy this URL and paste it into the Postmark Webhook URL field&lt;br&gt;
    - Check the box that will send the JSON payload to your Lambda function. EmailDrop parses this JSON to extract the attachments.&lt;br&gt;
    - Save your changes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qb0vo8f0iyl02rlka7o.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%2F8qb0vo8f0iyl02rlka7o.png" alt="Email attachments in your Google Drive folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the Integration&lt;/strong&gt;&lt;br&gt;
    - Send an email with attachments to your Postmark inbound email address&lt;br&gt;
    - The attachments should be automatically uploaded to your Google Drive folder&lt;br&gt;
    - Check the Lambda logs in &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html" rel="noopener noreferrer"&gt;CloudWatch&lt;/a&gt; if you encounter any issues&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kevinl95" rel="noopener noreferrer"&gt;
        kevinl95
      &lt;/a&gt; / &lt;a href="https://github.com/kevinl95/EmailDrop" rel="noopener noreferrer"&gt;
        EmailDrop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Automatically upload attachments to emails sent to your Postmark inbound email address to Google Drive
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;EmailDrop: Postmark to Google Drive Attachment Uploader&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/kevinl95/EmailDrop/actions/workflows/main.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/kevinl95/EmailDrop/actions/workflows/main.yml/badge.svg" alt="Lint CloudFormation Template"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This application automatically uploads email attachments from Postmark to Google Drive. When emails are sent to your Postmark inbound email address, any attachments are automatically saved to your specified Google Drive folder. The system uses AWS Lambda, API Gateway, and Secrets Manager to handle the OAuth flow and file uploads.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Postmark&lt;/strong&gt;: Receives emails and sends attachment data via webhooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt;: Provides endpoints for OAuth callback and Postmark webhook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Functions&lt;/strong&gt;: Handle OAuth flow and attachment uploads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt;: Securely stores OAuth refresh tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive API&lt;/strong&gt;: Destination for email attachments&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup Instructions&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Google Drive API Setup&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;1.1 Go to Google Cloud Console&lt;/h4&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://console.cloud.google.com/" rel="nofollow noopener noreferrer"&gt;https://console.cloud.google.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;1.2 Create a Project&lt;/h4&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Click the project dropdown → New Project&lt;/li&gt;
&lt;li&gt;Name it (e.g., PostmarkUploader)&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;1.3 Enable the Google Drive API&lt;/h4&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;APIs &amp;amp; Services&lt;/strong&gt; → &lt;strong&gt;Library&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Search…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kevinl95/EmailDrop" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;This project started as a simple idea: I wanted to extract attachments from emails and send them to Google Drive automatically. Drive does not offer this feature natively. Postmark’s Inbound Webhooks made that possible with a clean, reliable way to receive structured email data, including base64-encoded attachments, via a JSON payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1dll7vm3kzxl8hp8ht1.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%2Fi1dll7vm3kzxl8hp8ht1.png" alt="Architecture diagram of the CloudFormation stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used AWS CloudFormation to make the whole serverless solution deployable in one click. The stack provisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An OAuth token exchange Lambda: Handles the OAuth 2.0 flow with Google, exchanging an authorization code for tokens. It outputs a working Google OAuth link via CloudFormation so users can grant access easily.&lt;/li&gt;
&lt;li&gt;An upload Lambda: Triggered by Postmark’s inbound webhook. It decodes the attachments and uploads them to the authenticated user's Google Drive using the Drive API.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt;: Routes the Google OAuth redirect to the token exchange Lambda function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx591oyehyvc9uamh0e1n.jpg" 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%2Fx591oyehyvc9uamh0e1n.jpg" alt="Data flow diagram showing how attachments trigger a Postmark webhook which triggers the Lambda function that uploads the files to Google Drive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To keep things simple, I made sure the user experience didn't require running any local code or setting up servers. Once the stack is deployed, the user just needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up a Google Cloud project.&lt;/li&gt;
&lt;li&gt;Deploy the CloudFormation template with their OAuth credentials.&lt;/li&gt;
&lt;li&gt;Complete the auth flow by visiting a link output by the stack.&lt;/li&gt;
&lt;li&gt;Configure Postmark to send inbound emails as a JSON payload to the webhook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All credentials and logic are encapsulated in AWS Lambda functions using &lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python 3&lt;/a&gt; and the standard library, keeping dependencies minimal. Tokens are stored in &lt;a href="https://docs.aws.amazon.com/kms/latest/developerguide/overview.html" rel="noopener noreferrer"&gt;Amazon's Key Management Service&lt;/a&gt;, which lets EmailDrop securely manage all information needed to authenticate with the Google Drive API.&lt;/p&gt;

&lt;p&gt;My experience with Postmark was great, the inbound webhook fature is fast and extremely easy to consume. Having the full email (plus parsed fields and attachments) delivered via HTTP was a huge time-saver compared to working directly with IMAP or polling APIs. Being able to extract content from emails in a structured way will make a number of projects and tools much easier to build, and I look forward to using Postmark's inbound features again!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>api</category>
      <category>postmarkchallenge</category>
    </item>
    <item>
      <title>Data Broken - Opt out of the data broker nightmare with Privotron and Amazon Q Developer</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Mon, 12 May 2025 02:59:23 +0000</pubDate>
      <link>https://dev.to/mrmemory/data-broken-opt-out-of-the-data-broker-nightmare-with-privotron-and-amazon-q-developer-2omd</link>
      <guid>https://dev.to/mrmemory/data-broken-opt-out-of-the-data-broker-nightmare-with-privotron-and-amazon-q-developer-2omd</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aws-amazon-q-v2025-04-30"&gt;Amazon Q Developer "Quack The Code" Challenge&lt;/a&gt;: Crushing the Command Line&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kevinl95/Privotron" rel="noopener noreferrer"&gt;Privotron&lt;/a&gt; is a command-line automation tool that helps users opt out of invasive data broker databases. It targets the &lt;a href="https://www.cnbc.com/2024/10/11/internet-data-brokers-online-privacy-personal-information.html" rel="noopener noreferrer"&gt;expanding industry of data brokers&lt;/a&gt;, which collect, buy, and sell personal information, including home addresses, health conditions, religious or political affiliations, and behavioral profiles, often without meaningful consent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwo3k8g49zit3virty3l7.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%2Fwo3k8g49zit3virty3l7.png" alt="Infographic about Data brokers with the text "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opting out of these services is legally permitted in many jurisdictions but is intentionally made tedious: websites are inconsistent, forms are buried, and requests often require multiple steps. Privotron automates these processes, using browser automation to simulate human opt-out actions, saving users time and frustration.&lt;/p&gt;

&lt;p&gt;Privotron is built on a modern Python stack that leverages several powerful libraries for browser automation and configuration management. At its core, the application uses &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, a robust browser automation framework that provides cross-browser support and reliable DOM interaction capabilities. The command-line interface is implemented using &lt;a href="https://click.palletsprojects.com/en/stable/" rel="noopener noreferrer"&gt;Click&lt;/a&gt;, which enables sophisticated argument parsing and validation with minimal boilerplate code. For configuration management, Privotron employs &lt;a href="https://pypi.org/project/PyYAML/" rel="noopener noreferrer"&gt;PyYAML&lt;/a&gt; to parse broker-specific YAML files, allowing for declarative definitions of opt-out workflows. The architecture combines declarative configuration with imperative execution, allowing for a flexible and extensible system that can adapt to the varied requirements of different data broker opt-out processes.&lt;/p&gt;

&lt;p&gt;Crucially, Privotron is designed to be pluggable and community-driven. I've provided a detailed broker contribution guide to make it easy for users to add support for new brokers. The long-term goal is to create a crowd-sourced library of automation plugins. Currently, Privotron has three different data brokers to start.&lt;/p&gt;

&lt;p&gt;To this end &lt;a href="https://aws.amazon.com/q/developer/" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt; has been instrumental in making this application easy to extend by non-developers, allowing for the use of human-readable &lt;a href="https://yaml.org/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt; "playbooks" that explain exactly how the opt out should work. It also was crucial at helping write clear documentation with meaningful examples. It also automated adding a number of convenience features, like user profiles so users do not have to re-enter their details each time the repo updates with new data brokers. Let's dive in and see how it works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&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%2Fwi0se2lrof9odgp5dwbx.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%2Fwi0se2lrof9odgp5dwbx.png" alt="An example data broker opt-out form, asking for personally identifiable information"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a simple example of what a data broker opt-out form looks like. Many are far more invasive, asking for social security numbers and other sensitive pieces of information. While it doesn't look like much, the goal of Privotron is to fill out as many of these as possible with minimal user intervention. Every broker is unique and it can take a great deal of time to locate and work through each opt-out form, which is one of the main inconveniences Privotron attempts to alleviate. To this end, the CLI asks for some basic information it can combine in various ways to answer the various questions these forms may throw at it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv39xwng4rclbiog96bsx.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%2Fv39xwng4rclbiog96bsx.png" alt="Privotron command line interface help menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Users give Privotron some personally identifiable information. This data is sensitive and is only used by Privotron to fill in data broker forms, and is only stored if the user requests in a local profile file that they can delete at any time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiomftq8cf5d9nqeddync.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%2Fiomftq8cf5d9nqeddync.png" alt="Privotron submits an opt-out form and handles an anti-bot measure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon invoking Privotron with their profile or their information, Privotron will open a web browser and automatically start working through data broker opt out forms. Through clever lists of "actions" in each broker playbook, it fills required fields and handles various buttons and checkboxes. Using the user's own computer and residential IP helps alleviate some of the anti-bot measures these data brokers take, as shown above. I hope in the future to add better support for working through &lt;a href="https://en.wikipedia.org/wiki/CAPTCHA" rel="noopener noreferrer"&gt;captchas&lt;/a&gt; and other anti-bot measures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvh99utxdxqeliyvfexa2.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%2Fvh99utxdxqeliyvfexa2.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many brokers require users to select what records to remove. This requires some manual user intervention to validate the records they want deleted. Privotron will prompt the user and will continue when the user wishes to. This gives broker contributors flexibility in handling sites that have non-deduplicated data where users may come up in various different records they may wish to delete.&lt;/p&gt;

&lt;p&gt;Below is an example of the simplest opt-out playbook currently in Privotron. It shows how the playbooks are human readable and easy to debug.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: United States Phonebook
slug: unitedstatesphonebook
url: https://www.unitedstatesphonebook.com/
required_fields:
  - phone
  - zip
steps:
  - action: navigate
    url: "https://www.unitedstatesphonebook.com/contact.php"
  - action: fill
    selector: '[name="number"]'
    field: phone
  - action: fill
    selector: '[name="zip"]'
    field: zip
  - action: click
    selector: '[value="Request Removal"]'
  - action: wait
    seconds: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;




&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kevinl95" rel="noopener noreferrer"&gt;
        kevinl95
      &lt;/a&gt; / &lt;a href="https://github.com/kevinl95/Privotron" rel="noopener noreferrer"&gt;
        Privotron
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Framework and tool to make it easier to opt out of data brokers
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Privotron&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Privotron is an open-source framework and automation tool designed to help individuals reclaim their privacy by opting out of data brokers. Data brokers collect, store, and sell personal information without explicit consent, posing significant privacy risks. Privotron makes the opt-out process easier by automating browser interactions with data broker websites.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Privotron?&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy Protection&lt;/strong&gt;: Data brokers collect and sell your personal information without your explicit consent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Saving&lt;/strong&gt;: Manually opting out of dozens of data brokers can take hours or days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: Privotron automates the tedious process of filling out opt-out forms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracking&lt;/strong&gt;: Keep track of which brokers you've already opted out from&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community-Driven&lt;/strong&gt;: Easily contribute new broker configurations to help others&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.13 or higher&lt;/li&gt;
&lt;li&gt;poetry (for dependency management)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installing Poetry&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;If you don't have Poetry installed, you can install it using one of these methods:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended (Official installer):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -sSL&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kevinl95/Privotron" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Amazon Q Developer
&lt;/h2&gt;

&lt;p&gt;I primarily interacted with Amazon Q Developer &lt;a href="https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.amazon-q-vscode" rel="noopener noreferrer"&gt;through the excellent official extension for Visual Studio Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Amazon Q Developer was instrumental in helping add a number of quality-of-life features, as well as helping expand Privotron's "actions", which are the different ways it can interact with a Data Broker's webpage. A good example of this is how, once I got the core of the application running, Amazon Q was able to understand the data I was asking for from users and build me the profile feature that prevents users from needing to re-enter their information. All I needed to do is explain the feature, specify a minor detail that I wanted Click to have a new optional CLI argument, and Amazon Q built the feature for me as you see it in the project today:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw6dmm09ymtahizh1j8b.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%2Fjw6dmm09ymtahizh1j8b.png" alt="Amazon Q describing that it will implement a state file for user profile data using JSON"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also used Amazon Q Developer to help me handle edge cases in browser automation. As you can imagine, broker websites vary widely. I would frequently copy and paste new pieces of broker UI HTML into Amazon Q and it would update Privotron to handle these new edge cases. Here's an example of how it made the state selector action, which handles dropdowns for US states that are common on broker opt-out forms, much more flexible in seconds once I gave it an example of what I was trying to handle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqplcjv05rw91hu1rog7.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%2Fqqplcjv05rw91hu1rog7.png" alt="Amazon Q is prompted with an example of a US state selection dropdown in HTML and says it will adapt the code to handle this kind of UI widget"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the mapping it came up with, saving me some tedious manual work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F743ru65acgwhnuct787a.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%2F743ru65acgwhnuct787a.png" alt="A mapping of US states to their two-letter abreviations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was also able to ask it to make convenience functions for me. I already was collecting the user's first and last name, but needed to easily be able to fill in forms asking for the users full name. Amazon Q handled this new convenience action like a champ, and even anticipated that I may want the user's full name listed in the format "Lastname, Firstname" without be even asking!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr4ukrxkhlnb6joouaoc.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%2Fyr4ukrxkhlnb6joouaoc.png" alt="Amazon Q explaining a new Privotron action that lets the user choose to have a full name entered into an opt-out form, either forwards or backwards"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, Amazon Q Developer understood my markdown files and the code well enough that it wrote &lt;a href="https://github.com/kevinl95/Privotron/blob/main/BROKER_GUIDE.md" rel="noopener noreferrer"&gt;an excellent broker contribution guide&lt;/a&gt; which you can find in the repo right now! It has meaningful examples and describes the various actions Privotron can take on a broker opt-out page, making it the cornerstone of my strategy to crowdsource additional broker opt-out playbooks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfrsfhe8hhrmvmucjblw.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%2Ftfrsfhe8hhrmvmucjblw.png" alt="Amazon Q describing how it automatically wrote a broker contribution guide with detailed examples"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazon Q Developer saved hours of work and automated away the tedious bits of cleaning up a project to show off, putting me much farther ahead with this project than I would have otherwise been. It also anticipated a number of edge cases and conveniences that I likely would not have added in my first cut, making Privotron far more useful in this initial iteration than it otherwise would have been.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>awschallenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Did you know that you can discover where the businesses behind the products in your grocery store operate just by looking at the barcode? I’ve developed OriginTrackr, a free Progressive Web App that allows you to explore more about your favorite products!</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Wed, 26 Mar 2025 00:21:11 +0000</pubDate>
      <link>https://dev.to/mrmemory/did-you-know-that-you-can-discover-where-the-businesses-behind-the-products-in-your-grocery-store-dcl</link>
      <guid>https://dev.to/mrmemory/did-you-know-that-you-can-discover-where-the-businesses-behind-the-products-in-your-grocery-store-dcl</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/mrmemory" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F1628847%2F097e3404-a85d-4fbf-a107-d984c96fcf8f.jpeg" alt="mrmemory"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/mrmemory/origintrackr-explore-where-products-come-from-with-kendoreact-1g4h" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;OriginTrackr: Explore where products come from with KendoReact&lt;/h2&gt;
      &lt;h3&gt;Kevin ・ Mar 24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kendoreactchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OriginTrackr: Explore where products come from with KendoReact</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Mon, 24 Mar 2025 02:48:28 +0000</pubDate>
      <link>https://dev.to/mrmemory/origintrackr-explore-where-products-come-from-with-kendoreact-1g4h</link>
      <guid>https://dev.to/mrmemory/origintrackr-explore-where-products-come-from-with-kendoreact-1g4h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kendoreact"&gt;KendoReact Free Components Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&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%2Fynklxqse6d6b9hbzfdhu.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%2Fynklxqse6d6b9hbzfdhu.png" alt="A world map made of of a barcode is displayed over the text " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://origintrackr.com" rel="noopener noreferrer"&gt;OriginTrackr&lt;/a&gt; is an innovative &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app" rel="noopener noreferrer"&gt;progressive web app (PWA)&lt;/a&gt; designed to help consumers discover where the products they purchase may come from. There is a global movement to understand where the products we buy are coming from, or where the companies that make the products we buy every day are based. Built with React and powered by KendoReact components, the PWA brings together an intuitive interface that allows users to scan barcodes on products as they shop which &lt;a href="https://en.wikipedia.org/wiki/List_of_GS1_country_codes" rel="noopener noreferrer"&gt;reveals where the companies making or packaging the product's business is based&lt;/a&gt;. This can reveal trade connections that might not be apparent even if the product is labeled with where it is packaged or made. Recognizing the challenges of unreliable Wi-Fi in supermarkets and retail environments, I developed OriginTrackr as an offline-capable PWA to ensure users can access crucial product information without the need for a constant internet connection. Once users visit the application the KendoReact components, country code lookup table, and other assets are cached for offline use- no sketchy store WiFi required! Whether users are scanning a product’s barcode on the go or exploring detailed origin data, OriginTrackr ensures they can do so efficiently and offline.&lt;/p&gt;

&lt;p&gt;OriginTrackr checks the prefix of barcodes against hundreds of country codes using &lt;a href="https://www.npmjs.com/package/react-barcode-scanner" rel="noopener noreferrer"&gt;react-barcode-scanner&lt;/a&gt; and a locally cacheable list of country codes. &lt;a href="https://www.barcoding.com/blog/barcode-prefixes-and-product-country-of-origin" rel="noopener noreferrer"&gt;While a country code doesn't tell you where a product is necessarily made&lt;/a&gt;, it may inform consumers about where their money is potentially going as it does indicate that the business packaging or making that product is present in the country they registered their unique identifier with the &lt;a href="https://www.gs1.org/" rel="noopener noreferrer"&gt;GS1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check it out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyc0yffvdz62rq9aw12m.gif" 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%2Fpyc0yffvdz62rq9aw12m.gif" alt="Animation of a user using OriginTrackr to scan the barcode on a bottle of oil, discovering that its barcode was registered in the USA." width="648" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;You can try OriginTrackr out on your phone, tablet, or laptop right now at &lt;a href="https://origintrackr.com" rel="noopener noreferrer"&gt;OriginTrackr.com&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;OriginTrackr has a modern, clean UI using KendoReact components. The first thing you are greeted with is a button that will let you start scanning barcodes. You will be prompted for permission before it begins using your camera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvrru3snyfstw698vsjh.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%2Fdvrru3snyfstw698vsjh.png" alt="The main OriginTrackr UI, with a button asking if the user would like to scan and a data table of past scans displayed below it" width="720" height="1423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OriginTrackr will display a popup letting you know where this barcode was registered, giving you an idea of where the business is based:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq89luhefbxz7mr6c2dj2.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%2Fq89luhefbxz7mr6c2dj2.png" alt="OriginTrackr screenshot displaying that a product's barcode was registered in the UK" width="717" height="1421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyttewt2xja99ivjpu72.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%2Fhyttewt2xja99ivjpu72.png" alt="OriginTrackr screenshot displaying that a product's barcode was registered in Mexico" width="720" height="1424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you get this notification, you can take OriginTrackr offline- no data connection or WiFi required!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe7jg3r0bvqcbs2fjoqh.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%2Fwe7jg3r0bvqcbs2fjoqh.png" alt="Notification telling a user that OriginTrackr is now available offline" width="416" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also install OriginTrackr as an app by pressing "Add to Homescreen" from the settings menu in your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6jolx442tbg6nk4xx3w.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%2Fy6jolx442tbg6nk4xx3w.png" alt="Screenshot of the OriginTrackr installation prompt" width="434" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course the whole application is open source, so you can check out how I did this on GitHub!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kevinl95" rel="noopener noreferrer"&gt;
        kevinl95
      &lt;/a&gt; / &lt;a href="https://github.com/kevinl95/OriginTrackr" rel="noopener noreferrer"&gt;
        OriginTrackr
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Find out where the products you're buying come from, just by scanning their barcode
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OriginTrackr - Find out where your product choices are coming from using your camera&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/8cadc4305fbf93fcca1e7640f8a165213f7a0e60fc9e41275bbff43898fd6ba4/68747470733a2f2f6465706c6f792d62616467652e76657263656c2e6170702f3f75726c3d687474703a2f2f7777772e6f726967696e747261636b722e636f6d2f266e616d653d56657263656c"&gt;&lt;img src="https://camo.githubusercontent.com/8cadc4305fbf93fcca1e7640f8a165213f7a0e60fc9e41275bbff43898fd6ba4/68747470733a2f2f6465706c6f792d62616467652e76657263656c2e6170702f3f75726c3d687474703a2f2f7777772e6f726967696e747261636b722e636f6d2f266e616d653d56657263656c" alt="Website Deploy"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/kevinl95/OriginTrackrpublic/logo192.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fkevinl95%2FOriginTrackrpublic%2Flogo192.png" alt="A world map made out of a barcode with the text Origin Trackr displayed below it."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A modern web application to help you better identify where products come from during your errands! It lets you scan the barcodes on products using your cell phone camera and then uses the UPC to tell you where the product is coming from. It does this entirely offline- you can visit this page while you have internet and then re-visit the page while avoidng spotty or non-existant supermarket internet and still be able to see where your products are coming from!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Built with modern web technologies and featuring KendoReact free components, OriginTrackr provides a robust and user-friendly interface.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Technologies&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Frontend: React&lt;/li&gt;
&lt;li&gt;UI Components: KendoReact free components&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Barcode scanning&lt;/li&gt;
&lt;li&gt;UPC lookup by country&lt;/li&gt;
&lt;li&gt;Offline-capable&lt;/li&gt;
&lt;li&gt;Allows users to store scans they would like to remember to a handy data table. Items in this table are stored…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kevinl95/OriginTrackr" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  KendoReact Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.telerik.com/kendo-react-ui/components/free" rel="noopener noreferrer"&gt;KendoReact Free Components&lt;/a&gt; were leveraged throughout OriginTrackr and made building an intuitive UI quick and easy. Here are some of my favorite components:&lt;/p&gt;

&lt;h4&gt;
  
  
  AppBar from @progress/kendo-react-layout
&lt;/h4&gt;

&lt;p&gt;OriginTrackr uses the AppBar to display a header and footer for the page, providing space for branding as well as providing the user a link to the source code for the application. It's my hope that this helps drive pull requests and community involvement in the project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Card from @progress/kendo-react-layout
&lt;/h4&gt;

&lt;p&gt;I don't feel that it's good practice to immediately ask users for access to something as sensitive as their camera when they first load a page. At the center of OriginTrackr is a card with a button that lets the user decide when they're ready to start scanning for barcodes. This then prompts the user for access to their camera, and also lets me expand the card to show the camera's output. It makes it clear that the majority of the user's interactions will be taking place in the center of the screen.&lt;/p&gt;

&lt;h4&gt;
  
  
  ExpansionPanel from @progress/kendo-react-layout
&lt;/h4&gt;

&lt;p&gt;I wanted to provide a space where users could optionally store scanned barcodes that they've seen in case they wanted to review where the products in their shopping cart came from later. I didn't want this to take up much room on the screen, so I put it in a collapsible expansion panel. This made it easy to keep the UI clean and manageable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dialog from @progress/kendo-react-dialogs
&lt;/h4&gt;

&lt;p&gt;Users are given a dialog with the country's flag and name where a given product's barcode is registered once OriginTrackr successfully identifies it. It makes it very clear to the user that scanning was successful, and provides a way for me to prompt them to save this product for later. Countries and barcodes are saved locally in the user's browser. The Dialog component made making this whole interaction very easy to implement as it needed minimal customization to achieve my main objective - let the user know quickly where the product they're holding came from.&lt;/p&gt;

&lt;h4&gt;
  
  
  Notification from @progress/kendo-react-notification
&lt;/h4&gt;

&lt;p&gt;I'm very excited that OriginTrackr works offline, but struggled to figure out how to tell the user that a website can work offline. It's not intuitive, and most users aren't used to using pages in their browser this way. The KendoReact notification widget made it easy for me to tell the user the moment the service worker finished caching assets for offline use that they would be able to use this app offline. It let me put the information somewhere accessible but non-intrusive. It also made it very easy for me to let the user know if something was wrong of if their browser does not support offline features or PWAs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delightfully Designed
&lt;/h3&gt;

&lt;p&gt;The Progress ThemeBuilder helped me get the UI I wanted for my app customized quickly. One of my favorite things about it is that I was able to start with a nice set of preset colors that go together from the styles menu. I chose Default Green, as it had no obvious conflicts for color blind users and because given the subject matter &lt;a href="https://psychologily.com/color-green-in-psychology/" rel="noopener noreferrer"&gt;green is a calming color&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc9k9labg4eltg7co0r7.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%2Fpc9k9labg4eltg7co0r7.png" alt="Selecting OriginTrackr's style from a list provided by the Progress ThemeBuilder" width="304" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Something that was bothering me while building Default Green, however, is that the success color is also green! I wanted it to be visually distinct from the other green widgets in my UI. Fortunately ThemeBuilder made this easy. I selected the success color from the dropdown and was shown all variants my users may encounter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze3wzzwi9njx2injls7o.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%2Fze3wzzwi9njx2injls7o.png" alt="Image description" width="295" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the color picker, I changed it to cyan which was currently unused in my UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xsl0hcc7m3iu4uloylk.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%2F4xsl0hcc7m3iu4uloylk.png" alt="Image description" width="627" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conveniently, all other success colors changed hue with it! I didn't need to go through and decide which hue was better for emphasis or hovering- ThemeBuilder did that for me. As an artistically challenged individual, I am thankful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm0bilc41328hi1qrrbj.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%2Fkm0bilc41328hi1qrrbj.png" alt="Image description" width="627" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see this color throughout OriginTrackr's UI, as well as the excellent Default Green. I hope you find it calming and the intentional uses of color intuitive!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create your own Censorship-Resistant Links for X and More!</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 18 Feb 2025 03:00:58 +0000</pubDate>
      <link>https://dev.to/mrmemory/create-your-own-censorship-resistant-links-for-x-and-more-1bg9</link>
      <guid>https://dev.to/mrmemory/create-your-own-censorship-resistant-links-for-x-and-more-1bg9</guid>
      <description>&lt;p&gt;Are you tired of social platforms blocking important links? Whether you’re trying to share information or simply want to keep control over your links, I’ve got a solution for you. In this guide, I'll show you how to easily create a censorship-resistant redirect link that you can use anywhere — without needing to write a single line of code!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the Problem?
&lt;/h2&gt;

&lt;p&gt;Platforms like &lt;a href="https://x.com" rel="noopener noreferrer"&gt;X (formerly Twitter)&lt;/a&gt; can block or censor links to certain sites &lt;a href="https://techcrunch.com/2025/02/17/x-is-blocking-links-to-signal-a-secure-messaging-platform-used-by-federal-workers/" rel="noopener noreferrer"&gt;like they recently did to Signal.me&lt;/a&gt;, even when those links contain important information. A link shortener or redirect may work in the short term, but sites like &lt;a href="https://bitly.com/" rel="noopener noreferrer"&gt;Bitly&lt;/a&gt; and &lt;a href="https://tinyurl.com/app/login" rel="noopener noreferrer"&gt;TinyURL&lt;/a&gt; create new single points of failure when platforms block them instead. What we want to do instead is leverage unique domains, like one you may already own for your blog or business. &lt;a href="https://www.forbes.com/advisor/business/free-domain-name/" rel="noopener noreferrer"&gt;You could also register one for free&lt;/a&gt;! We will then deploy a simple, free redirect function to &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; (a free cloud hosting provider) that will redirect to your intended page for sharing on social media. This way we create an unwinnable game of whack-a-mole for sharing important information.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You’ll Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A custom domain — You can get one for free (I'll share how in a moment).&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; account — Don't worry, it's free and easy to sign up!&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; account — again, free and easy to set up.&lt;/li&gt;
&lt;li&gt;No technical experience required — Everything is set up for you!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Get a Custom Domain (Optional, But Recommended)
&lt;/h3&gt;

&lt;p&gt;While you can use the default Netlify subdomain, I recommend registering a custom domain. Having your own domain makes the link more trustworthy and as we discussed it will be less likely to be blocked. You can get a free domain from services like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.noip.com/" rel="noopener noreferrer"&gt;No-IP&lt;/a&gt;&lt;/strong&gt; – Free subdomains (&lt;code&gt;yourname.ddns.net&lt;/code&gt;) with CNAME support.

&lt;ul&gt;
&lt;li&gt;⚠️ Requires renewal every 30 days for free accounts.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://nic.eu.org/" rel="noopener noreferrer"&gt;EU.org&lt;/a&gt;&lt;/strong&gt; – Free &lt;code&gt;.eu.org&lt;/code&gt; domains with full DNS management.

&lt;ul&gt;
&lt;li&gt;⚠️ Requires manual approval, which can take time. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;You can follow the instructions on these sites to get a free domain.&lt;/p&gt;

&lt;p&gt;A professional domain can only cost you a couple of bucks a year though – .me and .info domains, for example, can be had for less than $5 annually! Here's a non-exhaustive list of places to look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.namecheap.com/" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;&lt;/strong&gt; – Frequently offers $1 &lt;code&gt;.xyz&lt;/code&gt; or &lt;code&gt;.online&lt;/code&gt; domains.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.cloudflare.com/products/registrar/" rel="noopener noreferrer"&gt;Cloudflare Registrar&lt;/a&gt;&lt;/strong&gt; – No markup, sells at wholesale prices.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://porkbun.com/" rel="noopener noreferrer"&gt;Porkbun&lt;/a&gt;&lt;/strong&gt; – Regularly has $1–$3 domain deals.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Registration is a quick process, and there are plenty of tutorials available on these sites if you need help buying your first domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Deploy to Netlify
&lt;/h3&gt;

&lt;p&gt;This part is easy! You don’t need to worry about setting anything up yourself — everything is already configured. Just follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kevinl95/FreeLink" rel="noopener noreferrer"&gt;Go to the FreeLink repository on GitHub.
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click the "Deploy to Netlify" button:&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%2Flq98jjkvz2rv3gf6apyg.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%2Flq98jjkvz2rv3gf6apyg.png" alt="Deploy to Netlify button on the GitHub repo" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you haven’t already, sign up for a free &lt;a href="https://netlify.app" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;Click "Connect to GitHub" and sign in with your GitHub account (or use one of their other providers)
&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%2Fvyghibyl4v1wd4rpt8eu.png" alt="Connecting Netlify to GitHub" width="800" height="670"&gt;
&lt;/li&gt;
&lt;li&gt;When prompted, paste in the URL you would like your domain to redirect to into the form:&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%2Fpouefav1epvqo4e73m70.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%2Fpouefav1epvqo4e73m70.png" alt="Adding the redirect URL" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Save &amp;amp; Deploy" and wait a few moments for Netlify to set everything up.&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%2Fxrqkgpijq303jkvr5h0f.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%2Fxrqkgpijq303jkvr5h0f.png" alt="Netlify deployment" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once deployed, you’ll have your own censorship-resistant redirect link that you can use anywhere!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmx5dilual7ztdf119gtj.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%2Fmx5dilual7ztdf119gtj.png" alt="Successful deployment" width="753" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a successful deployment, you can click "Open production deploy" to try your new redirect page out!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add your Custom Domain
&lt;/h3&gt;

&lt;p&gt;Once the application is deployed,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Site Overview" in the side bar&lt;/li&gt;
&lt;li&gt;Scroll to "Set up your Site" as shown:&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%2Fgwmqega5spavc17di67t.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%2Fgwmqega5spavc17di67t.png" alt="Site setup menu" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Buy a new domain" or "set up a domain you already own". If you registered a new domain, the registrar should provide you with the necessary DNS settings. Follow Netlify's on-screen instructions to connect your domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once connected, your redirect link will use your custom domain instead of the default Netlify subdomain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That’s it! You’ve now created your own censorship-resistant link using Netlify, and you didn’t need to write any code. Simply deploy the project, set your redirect URL, and you’re ready to go.&lt;/p&gt;

&lt;p&gt;With your own custom domain you’ll be able to share links that can’t be easily censored. Feel free to share your new link with friends, family, or the community!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>socialmedia</category>
      <category>javascript</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
