<?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: Akash Panchal</title>
    <description>The latest articles on DEV Community by Akash Panchal (@akashp1712).</description>
    <link>https://dev.to/akashp1712</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%2F810251%2F33cc202d-6ac5-4b9c-b1fc-89f7aa4683b5.jpeg</url>
      <title>DEV Community: Akash Panchal</title>
      <link>https://dev.to/akashp1712</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akashp1712"/>
    <language>en</language>
    <item>
      <title>Configuring HTTP Proxy for gRPC in C# Without Environment Variables</title>
      <dc:creator>Akash Panchal</dc:creator>
      <pubDate>Sat, 13 Dec 2025 12:50:48 +0000</pubDate>
      <link>https://dev.to/akashp1712/configuring-http-proxy-for-grpc-in-c-without-environment-variables-4a9n</link>
      <guid>https://dev.to/akashp1712/configuring-http-proxy-for-grpc-in-c-without-environment-variables-4a9n</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;You have a gRPC client in C# using &lt;code&gt;Grpc.Core&lt;/code&gt; that needs to route traffic through an HTTP proxy. Sounds simple, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not quite.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've searched for solutions, you've probably found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;http_proxy&lt;/code&gt; environment variable ✅ Works, but affects ALL HTTP traffic&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Grpc.Net.Client&lt;/code&gt; with &lt;code&gt;HttpClientHandler.Proxy&lt;/code&gt; ✅ Clean, but requires library migration&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;grpc.http_proxy&lt;/code&gt; channel option ❌ Doesn't work in Grpc.Core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed &lt;strong&gt;per-channel proxy configuration&lt;/strong&gt; without affecting other traffic and without migrating libraries. So I dove into the gRPC C-core source code to understand how &lt;code&gt;http_proxy&lt;/code&gt; actually works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code References (gRPC v1.10.0)
&lt;/h3&gt;

&lt;p&gt;If you want to explore the internals yourself, here are the key files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/grpc/grpc/blob/v1.10.0/src/core/ext/filters/client_channel/http_proxy.cc" rel="noopener noreferrer"&gt;&lt;code&gt;http_proxy.cc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reads &lt;code&gt;http_proxy&lt;/code&gt; env var, sets channel args&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/grpc/grpc/blob/v1.10.0/src/core/ext/filters/client_channel/http_connect_handshaker.cc" rel="noopener noreferrer"&gt;&lt;code&gt;http_connect_handshaker.cc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Sends HTTP CONNECT request to proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/grpc/grpc/blob/v1.10.0/src/core/ext/transport/chttp2/client/secure/secure_channel_create.cc" rel="noopener noreferrer"&gt;&lt;code&gt;secure_channel_create.cc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSL/TLS target name override handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/grpc/grpc/blob/v1.10.0/src/core/lib/surface/channel.cc" rel="noopener noreferrer"&gt;&lt;code&gt;channel.cc&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Default authority header logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/grpc/grpc/blob/v1.10.0/src/csharp/Grpc.Core/ChannelOptions.cs" rel="noopener noreferrer"&gt;&lt;code&gt;ChannelOptions.cs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C# channel option constants&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;When gRPC honors the &lt;code&gt;http_proxy&lt;/code&gt; environment variable, it doesn't do anything magical. It simply:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses the proxy URL&lt;/li&gt;
&lt;li&gt;Sets internal channel arguments&lt;/li&gt;
&lt;li&gt;Uses these arguments during connection&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: &lt;strong&gt;these channel arguments are accessible via &lt;code&gt;ChannelOption&lt;/code&gt; in C#!&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Understanding HTTP CONNECT Tunneling
&lt;/h3&gt;

&lt;p&gt;HTTP proxies use the CONNECT method to create TCP tunnels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────┐         ┌───────┐         ┌────────────┐
│ Client │   TCP   │ Proxy │   TCP   │ gRPC Server│
└───┬────┘         └───┬───┘         └─────┬──────┘
    │                  │                   │
    │ TCP Connect      │                   │
    │─────────────────►│                   │
    │                  │                   │
    │ CONNECT host:port HTTP/1.1          │
    │─────────────────►│                   │
    │                  │                   │
    │                  │ TCP Connect       │
    │                  │──────────────────►│
    │                  │                   │
    │ HTTP/1.1 200 Connection established │
    │◄─────────────────│                   │
    │                  │                   │
    │◄──────────── TCP Tunnel ───────────►│
    │         (TLS + gRPC flows here)      │
    │                  │                   │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the tunnel is established, TLS handshake and gRPC communication flow through transparently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Magic Channel Options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt; &lt;span class="nf"&gt;CreateChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SslCredentials&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;proxyEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proxyEndpoint&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract hostname without port for SSL validation&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;targetHost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 1. HTTP CONNECT tunnel target (host:port)&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChannelOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grpc.http_connect_server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// 2. SSL SNI + certificate validation (hostname only)&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChannelOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChannelOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SslTargetNameOverride&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="c1"&gt;// 3. HTTP/2 :authority header (host:port)&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChannelOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChannelOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAuthority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Channel connects to PROXY, tunnels to TARGET&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Direct connection&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Each Option Does
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;grpc.http_connect_server&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Tells gRPC where to tunnel&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt; When gRPC connects to the channel target (the proxy), it sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Format:&lt;/strong&gt; &lt;code&gt;host:port&lt;/code&gt; (port is required!)&lt;/p&gt;




&lt;h4&gt;
  
  
  2. &lt;code&gt;SslTargetNameOverride&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; SSL/TLS hostname for SNI and certificate validation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Without this, gRPC would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send SNI for the proxy hostname&lt;/li&gt;
&lt;li&gt;Validate the certificate against the proxy hostname&lt;/li&gt;
&lt;li&gt;Both are WRONG — we need the actual target's certificate!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS ClientHello contains correct SNI: &lt;code&gt;api.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Certificate validation checks against: &lt;code&gt;api.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Format:&lt;/strong&gt; &lt;code&gt;hostname&lt;/code&gt; (NO port — SSL certificates don't include ports)&lt;/p&gt;




&lt;h4&gt;
  
  
  3. &lt;code&gt;DefaultAuthority&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Sets the HTTP/2 &lt;code&gt;:authority&lt;/code&gt; pseudo-header&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; gRPC servers use &lt;code&gt;:authority&lt;/code&gt; for routing. Without this override, it would be set to the proxy address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:method: POST
:scheme: https
:authority: api.example.com:443  ← Correct!
:path: /mypackage.MyService/MyMethod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Format:&lt;/strong&gt; &lt;code&gt;host:port&lt;/code&gt; (port often required for server routing)&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete Flow Diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                        YOUR APPLICATION                          │
│  Channel target: proxy-server:8080                              │
│  Options:                                                        │
│    grpc.http_connect_server  = "api.example.com:443"            │
│    SslTargetNameOverride     = "api.example.com"                │
│    DefaultAuthority          = "api.example.com:443"            │
└──────────────────────────────┬──────────────────────────────────┘
                               │
          Step 1: TCP Connect  │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                        HTTP PROXY                                │
│  Receives: CONNECT api.example.com:443 HTTP/1.1                 │
│  Action:   Opens TCP connection to api.example.com:443          │
│  Returns:  HTTP/1.1 200 Connection established                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
          Step 2: TCP Tunnel   │ (transparent passthrough)
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                     TLS HANDSHAKE                                │
│  ClientHello SNI: "api.example.com"    (SslTargetNameOverride)  │
│  Server sends certificate for: "api.example.com"                │
│  Client validates cert against: "api.example.com" ✓             │
│  [mTLS: Client certificate sent if configured]                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
          Step 3: Encrypted    │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                      gRPC SERVER                                 │
│  Receives HTTP/2 request:                                       │
│    :authority = "api.example.com:443"    (DefaultAuthority)     │
│    :path = /mypackage.MyService/MyMethod                        │
│  Routes and processes request ✓                                 │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Not Just Use Environment Variables?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;http_proxy&lt;/code&gt; env var&lt;/td&gt;
&lt;td&gt;Global (all HTTP)&lt;/td&gt;
&lt;td&gt;May break other services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Channel Options&lt;/td&gt;
&lt;td&gt;Per-channel&lt;/td&gt;
&lt;td&gt;Isolated, controlled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Environment variables are global. If your application makes HTTP calls to multiple services, and only ONE needs proxying, you can't use environment variables safely.&lt;/p&gt;

&lt;p&gt;Channel options give you &lt;strong&gt;surgical precision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By understanding how gRPC handles proxies internally, we can configure per-channel proxy support without environment variables, keeping our other HTTP traffic unaffected.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you faced similar challenges with gRPC proxying? Drop a comment below!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>grpc</category>
      <category>proxy</category>
      <category>networking</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bootstrap a saas in a month, Here is How I did it</title>
      <dc:creator>Akash Panchal</dc:creator>
      <pubDate>Wed, 25 Oct 2023 00:21:36 +0000</pubDate>
      <link>https://dev.to/akashp1712/bootstrap-a-saas-in-a-month-here-is-how-i-did-it-bn3</link>
      <guid>https://dev.to/akashp1712/bootstrap-a-saas-in-a-month-here-is-how-i-did-it-bn3</guid>
      <description>&lt;p&gt;How I bootstrapped a micro SaaS using Amazon’s Leadership Principles&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UxJE5LJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AL9ooEEzD2SPjrkxygZcG-g.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UxJE5LJe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AL9ooEEzD2SPjrkxygZcG-g.jpeg" alt="https://www.amazon.jobs/content/en/our-workplace/leadership-principles" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two months ago, I made the decision to leave my software engineering role at Amazon and pursue my goal of building a bootstrapped SaaS startup. The idea was for a text summarization tool that could condense content while maintaining key context — something I knew I would use constantly as an avid online reader.&lt;/p&gt;

&lt;p&gt;With only personal savings backing me, I needed to build and validate an MVP quickly and efficiently. Luckily, my (almost) 2 years at Amazon ingrained &lt;a href="https://www.amazon.jobs/content/en/our-workplace/leadership-principles"&gt;&lt;strong&gt;leadership principles&lt;/strong&gt;&lt;/a&gt; that would prove invaluable as a scrappy solo founder.&lt;/p&gt;

&lt;p&gt;Here is a detailed look at that journey building &lt;a href="https://lessentext.com"&gt;&lt;strong&gt;LessenText &lt;/strong&gt;&lt;/a&gt;— a text summarization tool for efficiently condensing content — as a solo, technical founder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Validating Demand (Months 1–2)
&lt;/h3&gt;

&lt;p&gt;Sure there were existing summarization tools, but they lacked the control and customization I wanted. I started envisioning a SaaS focused specifically on summarizing longer written content while preserving key context. And allowing users to easily adjust and refine the generated text themselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One could argue that, chatGPT can help for free. But this time around I’m solving the UX problem not the Technical problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thus, before investing significant time, I built a simple landing page using &lt;a href="https://yep.so/p/lessentextai"&gt;&lt;strong&gt;yep.so&lt;/strong&gt;&lt;/a&gt; to validate interest. Over 3–4 months I drove 100 beta signups with minimal promotion. My goal was to gauge interest and collect emails from potential customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PqZ3lVcR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AQo_ReWLMkT8UY5QzzVPFPA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PqZ3lVcR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AQo_ReWLMkT8UY5QzzVPFPA.png" alt="" width="800" height="547"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://yep.so/p/lessentextai"&gt;https://yep.so/p/lessentextai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Seeing that initial demand gave me conviction there was a need I could fill better than competitors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Designing an Intuitive User Experience (Weeks 1–2)
&lt;/h3&gt;

&lt;p&gt;With demand validated, I focused on UX and UI design. As an ex-Amazonian, the &lt;strong&gt;Customer Obsession&lt;/strong&gt; principle was ingrained in me. So I wanted to craft an intuitive, seamless experience for generating summaries.&lt;/p&gt;

&lt;p&gt;I explored products like ChatGPT to understand current limitations and pain points. Two big gaps were lack of summarization controls and exclusive features focusing text summarization.&lt;/p&gt;

&lt;p&gt;Keeping these user-centric insights top of mind, I sketched out user flows and wireframes. I wanted clean, intuitive interfaces that solved those key issues.&lt;/p&gt;

&lt;p&gt;I also started learning NextJS during this design phase to prepare for rapidly building my vision as prototype code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Developing the MVP Version (Weeks 3–6)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RhYKBSCh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/804/1%2AsaYtwp5xY83MgUklfvH1wQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RhYKBSCh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/804/1%2AsaYtwp5xY83MgUklfvH1wQ.png" alt="" width="800" height="522"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://productschool.com/resources/glossary/mvp-minimum-viable-product"&gt;https://productschool.com/resources/glossary/mvp-minimum-viable-product&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  POC(proof of concept) vs MVP(minimum viable product)
&lt;/h4&gt;

&lt;p&gt;I really believe that a POC should focus on core proposition but an MVP should be close to the real product.&lt;/p&gt;

&lt;p&gt;See the POC at, &lt;a href="https://alpha.lessentext.com/"&gt;&lt;strong&gt;https://alpha.lessentext.com/&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;.&lt;/strong&gt; This POC really helped me brainstorm the ideas and features (with myself 😋)&lt;/p&gt;

&lt;p&gt;What I wanted to focus on is creating an MVP version which can be a whole product and the features can be developed on the top of it, without starting from the scratch.&lt;/p&gt;

&lt;h4&gt;
  
  
  MVP(minimum viable product)
&lt;/h4&gt;

&lt;p&gt;With design direction set, it was time to build. I set an ambitious goal of getting a functioning MVP online in 6 weeks. To achieve that timeline as a non-technical solo founder, I heavily leveraged some of the core Amazon leadership principles ingrained in me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve also came up with 1-pager memo like Amazonins do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. Customer Obsession&lt;/strong&gt; kept me focused on solving real user problems like lack of summarization controls and reading/savings features. I refused to get distracted by non-essential features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Dive Deep&lt;/strong&gt; enabled me to ramp up with frontend technologies. I had previously worked in Web development during PHP and jQuery era.&lt;/p&gt;

&lt;p&gt;I brushed Javascript, learned enough ReactJS and NextJS by studying obsessively and testing code examples, I gained enough skills to build my UI mockups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ownership&lt;/strong&gt; meant wearing many hats as a one-person team — not just coding, but also researching, testing, infrastructure setup and more. My experience at Amazon running projects end-to-end gave me confidence I could handle this variety.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good to know:&lt;/strong&gt; there are no devops team at Amazon. Engineering teams are responsible for setting and monitoring their own infra.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I often had to study and iterate &lt;strong&gt;pricing strategy&lt;/strong&gt; by calculating usage cost and customer pricing. It was overwhelming for me at first but I’m satisfied with the outcome and the learnings. It’s an ongoing process for any business whether be a large or small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt;  &lt;strong&gt;Frugality&lt;/strong&gt; led me to use free tiers from tools like Vercel, PlanetScale, and Railway while in beta mode. I wanted to prove product-market fit before incurring greater costs.&lt;/p&gt;

&lt;p&gt;The 6 week sprint was grueling, with many late nights spent coding. But seeing my vision turn into a real product was tremendously exciting. In the end, I was able to launch an initial version of LessenText to a small waitlist right on schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Gathering Feedback from Early Users (Week 7+)
&lt;/h3&gt;

&lt;p&gt;With a functioning beta product released, my goal shifted to learning. I wanted to gather feedback from early adopters to guide improvements before wider rollout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fnkz-aik--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1016/1%2ABYjwbr_0TxWq70cM62tRlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fnkz-aik--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1016/1%2ABYjwbr_0TxWq70cM62tRlw.png" alt="" width="800" height="99"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://lessentext.com"&gt;https://lessentext.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I also took help from a friend and family guy to try out And got some hidden bugs and interesting perspective from them.&lt;/p&gt;

&lt;p&gt;Ongoing refinement based on real usage, monitoring churn metrics, and engaging with customers will be crucial. Thanks to lessons from Amazon, ignoring user feedback is never an option for me.&lt;/p&gt;

&lt;p&gt;Now, If you’ve come this far, please try out &lt;a href="https://lessentext.com"&gt;https://lessentext.com&lt;/a&gt; free tier. And if you’re a student or a researcher, please connect me at &lt;a href="mailto:contact@lessentext.com"&gt;contact@lessentext.com&lt;/a&gt; to get a minimal profit discount.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuing the Journey
&lt;/h3&gt;

&lt;p&gt;Reaching an initial launch was a major milestone. But building a successful bootstrapped SaaS that customers love is a never-ending journey.&lt;/p&gt;

&lt;p&gt;The core Amazon leadership principles that enabled me to ship an MVP fast remain just as relevant today. I’m still customer obsessed, diving deep on technical topics, and reluctant to overspend.&lt;/p&gt;

&lt;p&gt;But I’ve also learned how challenging it is working alone for long periods. Staying motivated with no team for support or alignment is draining.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resources helped me during my journey
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Technical learnings&lt;/strong&gt; (&lt;a href="https://scrimba.com/learn/learnjavascript"&gt;Javascript&lt;/a&gt;, &lt;a href="https://www.udemy.com/course/react-the-complete-guide-incl-redux/"&gt;ReactJS&lt;/a&gt;, &lt;a href="https://www.udemy.com/course/nextjs-react-the-complete-guide/"&gt;NextJS&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=ffJ38dBzrlY&amp;amp;pp=ygUQYnVpbGRpbmcgYWkgc2Fhcw%3D%3D"&gt;Building AI saas&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free infra resources (&lt;/strong&gt;&lt;a href="http://vercel.com/"&gt;Vercel&lt;/a&gt;, &lt;a href="https://planetscale.com/"&gt;Planetscale&lt;/a&gt;, &lt;a href="http://clerk.com/"&gt;Clerk&lt;/a&gt;, &lt;a href="https://highlight.io/"&gt;Highlight&lt;/a&gt;, &lt;a href="https://railway.app/"&gt;Railway&lt;/a&gt;&lt;strong&gt;)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing Strategy &lt;/strong&gt; — paddle blog (&lt;a href="https://www.paddle.com/blog"&gt;https://www.paddle.com/blog&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;




</description>
      <category>bootstrapping</category>
      <category>microsaas</category>
      <category>founderstories</category>
      <category>amazon</category>
    </item>
  </channel>
</rss>
