<?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>Forem: Zakir Hossen</title>
    <description>The latest articles on Forem by Zakir Hossen (@devzakir).</description>
    <link>https://forem.com/devzakir</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%2F163072%2F9ff4253a-db01-4b12-acb4-778d9e50748b.png</url>
      <title>Forem: Zakir Hossen</title>
      <link>https://forem.com/devzakir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/devzakir"/>
    <language>en</language>
    <item>
      <title>Building an In-App E-Signature Flow Without DocuSign (Laravel + React + Canvas)</title>
      <dc:creator>Zakir Hossen</dc:creator>
      <pubDate>Wed, 15 Apr 2026 07:04:41 +0000</pubDate>
      <link>https://forem.com/devzakir/building-an-in-app-e-signature-flow-without-docusign-laravel-react-canvas-1a0m</link>
      <guid>https://forem.com/devzakir/building-an-in-app-e-signature-flow-without-docusign-laravel-react-canvas-1a0m</guid>
      <description>&lt;p&gt;A customer of mine was printing offer letters, signing them with a pen, scanning them on a flatbed, and emailing them back. In 2026. Inside an app I built for him.&lt;/p&gt;

&lt;p&gt;So last week I shipped a proper offer-letter workflow with an in-browser e-signature surface — no DocuSign, no third-party embed, no bolt-on fees. Just a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, a signed payload, and a stored PDF.&lt;/p&gt;

&lt;p&gt;Here's what I learned. The stack is &lt;strong&gt;Laravel 12 + Inertia v2 + React 19 + Mantine + Tailwind v4&lt;/strong&gt;, but most of this is transferable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem in one paragraph
&lt;/h2&gt;

&lt;p&gt;An offer letter flow has four moving parts: (1) a templated document with variables (name, salary, start date), (2) a public URL a candidate can open without logging in, (3) a signing surface that works on phone and desktop, and (4) a signed PDF that gets archived and is legally defensible. None of these are hard individually. The interesting part is where they collide.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The public signing URL — stop being clever
&lt;/h2&gt;

&lt;p&gt;First instinct: JWTs. Signed URLs. Short expiry. A whole dance.&lt;/p&gt;

&lt;p&gt;What I actually shipped: a random 64-char token on the &lt;code&gt;offers&lt;/code&gt; table, looked up on a route-model-binding &lt;code&gt;where('token', $token)-&amp;gt;firstOrFail()&lt;/code&gt;. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/web.php&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/offer/{token}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;CandidateOfferController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'show'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/offer/{token}/accept'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;CandidateOfferController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'accept'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is the secret. No login, no account creation, no email confirmation round-trip. The candidate clicks the link and sees the letter. That's the entire auth model for the candidate side.&lt;/p&gt;

&lt;p&gt;If you're thinking "but what about security" — the token has ~380 bits of entropy, the route is HTTPS only, and every action is idempotent and logged. DocuSign's own "anyone with the link can sign" flow has the same threat model. Don't over-engineer this.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The signature canvas — dark mode will bite you
&lt;/h2&gt;

&lt;p&gt;I used &lt;code&gt;react-signature-canvas&lt;/code&gt;, which wraps the excellent &lt;code&gt;signature_pad&lt;/code&gt; library. The naive integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignatureCanvas&lt;/span&gt;
  &lt;span class="na"&gt;penColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"var(--mantine-color-text)"&lt;/span&gt;
  &lt;span class="na"&gt;canvasProps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w-full h-48 border rounded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This silently fails in dark mode.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; 2D context cannot resolve CSS custom properties. It sees &lt;code&gt;var(--mantine-color-text)&lt;/code&gt; as a literal string, fails to parse it, and falls back to... nothing visible. The user draws, the strokes are captured, but they're rendered in a color the canvas can't compute. You get a "signature captured!" toast and a completely blank canvas.&lt;/p&gt;

&lt;p&gt;The fix is to resolve the color at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useComputedColorScheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mantine/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colorScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useComputedColorScheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;getInitialValueInEffect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;penColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colorScheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f1f3f5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1a1a1a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvasBg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colorScheme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1a1b1e&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignatureCanvas&lt;/span&gt;
  &lt;span class="na"&gt;penColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;penColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;canvasBg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;canvasProps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w-full h-48 border rounded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Canvas doesn't know about CSS. Always pass hex. This cost me an embarrassing amount of debugging time.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Type-to-sign — the 40% of users who hate drawing
&lt;/h2&gt;

&lt;p&gt;Half your users are on a laptop with a trackpad. Drawing a signature with a trackpad looks like a seismograph readout. You need a second input mode: &lt;strong&gt;type your name, render it in a cursive font&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TYPED_FONT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"Dancing Script", "Brush Script MT", "Lucida Handwriting", cursive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderTypedSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TYPED_FONT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Auto-fit: shrink until it fits&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fontSize&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TYPED_FONT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textAlign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textBaseline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;middle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;penColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two gotchas here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Load the font explicitly.&lt;/strong&gt; Don't assume Dancing Script is on the user's system. Load it via &lt;code&gt;&amp;lt;Head&amp;gt;&lt;/code&gt; in Inertia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Dancing+Script:wght@600&amp;amp;display=swap"&lt;/span&gt;
    &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Export the same format as the drawn version.&lt;/strong&gt; Call &lt;code&gt;canvas.toDataURL('image/png')&lt;/code&gt; in both modes. Your backend then has a single format to handle. The user picks between Draw and Type via a &lt;code&gt;&amp;lt;SegmentedControl&amp;gt;&lt;/code&gt;, and the output is identical from the server's perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The signed payload — keep it boring
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;OfferSignature&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'offer_id'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'signer_name'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'signer_name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'signer_email'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'signer_email'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'signature_data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'signature_data'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// PNG dataURL&lt;/span&gt;
    &lt;span class="s1"&gt;'ip_address'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;'user_agent'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;'signed_at'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'status'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;OfferStatus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'accepted_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IP + user agent + timestamp is the legal audit trail. You don't need a blockchain. You don't need KBA. For employment offers, this is the standard the US ESIGN Act and EU eIDAS Simple Electronic Signature actually require. Ask a lawyer for your jurisdiction before you believe me, but the legal bar is much lower than the SaaS marketing suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The signed PDF — generate it, store it, forget it
&lt;/h2&gt;

&lt;p&gt;Once the candidate signs, kick off a job that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Renders the letter HTML to PDF (I use &lt;code&gt;spatie/browsershot&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Embeds the signature image at the bottom with the signer's name, typed timestamp, and IP.&lt;/li&gt;
&lt;li&gt;Stores it in object storage (Cloudflare R2 in my case — free egress is the whole game).&lt;/li&gt;
&lt;li&gt;Makes it downloadable from both the employer dashboard and the candidate's public URL.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateOfferPdfService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generateAndStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Offer&lt;/span&gt; &lt;span class="nv"&gt;$offer&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="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;renderHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$pdf&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Browsershot&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'A4'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"offers/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/signed.pdf"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'r2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. The pipeline trigger — where the magic actually is
&lt;/h2&gt;

&lt;p&gt;Here's the bit that separates "e-signature tool" from "offer-letter workflow." When the candidate signs, I also fire a state transition on the job application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Offer&lt;/span&gt; &lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$signatureData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... create signature, update status ...&lt;/span&gt;

    &lt;span class="nv"&gt;$application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$offer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jobApplication&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="nv"&gt;$application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$hiredStage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$application&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hired'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&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="nv"&gt;$hiredStage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$application&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$hiredStage&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The candidate slides from "Offer Sent" to "Hired" in the Kanban board the moment they sign. The employer doesn't touch anything. This one line of code is the reason "offer letter as a feature of the ATS" is 10x better than "offer letter as a standalone SaaS."&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;The interesting part of building this wasn't any single piece. It was that the &lt;strong&gt;seams between pieces are where products actually differentiate&lt;/strong&gt;. A standalone e-signature tool can't move a candidate to "Hired" because it doesn't know what a pipeline is. A standalone ATS can't reliably track a signed offer because it doesn't own the signing surface. Owning both means you get to delete five manual steps from the workflow, and nobody else can.&lt;/p&gt;

&lt;p&gt;If you're building in a mature SaaS market and you can't find a differentiator — look at the integrations your users are duct-taping together, and build one of them natively. The seams are where the leverage lives.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Zakir, solo founder of &lt;a href="https://jugglehire.com" rel="noopener noreferrer"&gt;JuggleHire&lt;/a&gt;. We shipped this feature yesterday and launched it on Product Hunt today. If you want to see the flow in action or roast my code, the app is at &lt;a href="https://jugglehire.com" rel="noopener noreferrer"&gt;jugglehire.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Non-technical write-up on the "why" is on &lt;a href="https://jugglehire.com/blog/hiring-got-modernized-offer-letter-didnt" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built an AI Changelog Tool in 3 Days — Looking for Beta Testers</title>
      <dc:creator>Zakir Hossen</dc:creator>
      <pubDate>Tue, 17 Feb 2026 13:09:08 +0000</pubDate>
      <link>https://forem.com/devzakir/i-built-an-ai-changelog-tool-in-3-days-looking-for-beta-testers-3a45</link>
      <guid>https://forem.com/devzakir/i-built-an-ai-changelog-tool-in-3-days-looking-for-beta-testers-3a45</guid>
      <description>&lt;p&gt;Hey Dev community! 👋&lt;/p&gt;

&lt;p&gt;I just mass launched ShipTell after 3 days of intense building (9 hours today alone).&lt;/p&gt;

&lt;p&gt;What is ShipTell?&lt;/p&gt;

&lt;p&gt;An AI-powered changelog generator for developers and founders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Connect your GitHub repo&lt;/li&gt;
&lt;li&gt;AI reads your commits/PRs&lt;/li&gt;
&lt;li&gt;Generates a polished changelog&lt;/li&gt;
&lt;li&gt;Publish to a public page or embed on your site&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;I'm a solo founder running multiple SaaS products. I ship features constantly but always neglect changelogs.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;Users deserve to know what's new. We deserve a tool that doesn't make it feel like a chore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;• Laravel + React (Inertia.js)&lt;br&gt;
• PostgreSQL&lt;br&gt;
• GitHub OAuth&lt;br&gt;
• AI for content generation&lt;br&gt;
• Tailwind CSS&lt;/p&gt;

&lt;p&gt;Looking for 10 Beta Users&lt;/p&gt;

&lt;h2&gt;
  
  
  If you:
&lt;/h2&gt;

&lt;p&gt;• Ship code regularly&lt;br&gt;
• Want to communicate updates to users&lt;br&gt;
• Hate writing changelogs manually&lt;br&gt;
I want your feedback.&lt;/p&gt;

&lt;p&gt;Try it free: shiptell.com (&lt;a href="https://shiptell.com/" rel="noopener noreferrer"&gt;https://shiptell.com/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Drop a comment if you're interested! 🚀&lt;/p&gt;

</description>
      <category>github</category>
      <category>productivity</category>
      <category>saas</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>How I Built a SaaS with Laravel + React + Inertia.js (Full Stack Breakdown)</title>
      <dc:creator>Zakir Hossen</dc:creator>
      <pubDate>Thu, 05 Feb 2026 08:56:20 +0000</pubDate>
      <link>https://forem.com/devzakir/how-i-built-a-saas-with-laravel-react-inertiajs-full-stack-breakdown-125k</link>
      <guid>https://forem.com/devzakir/how-i-built-a-saas-with-laravel-react-inertiajs-full-stack-breakdown-125k</guid>
      <description>&lt;p&gt;I've been building JuggleHire—a hiring tool for small teams—for 2.5 years. Here's the stack and why I chose 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%2Fb2yjqrqcd6eagr3rmehh.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%2Fb2yjqrqcd6eagr3rmehh.png" alt=" " width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Laravel 12 (PHP 8.3)&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Redis (queues + caching)&lt;/li&gt;
&lt;li&gt;Laravel Horizon for queue management&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;Inertia.js v2 (the glue)&lt;/li&gt;
&lt;li&gt;Mantine UI components&lt;/li&gt;
&lt;li&gt;Tailwind CSS v4&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;DigitalOcean&lt;/li&gt;
&lt;li&gt;Laravel Forge&lt;/li&gt;
&lt;li&gt;GitHub Actions for CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Inertia.js?
&lt;/h2&gt;

&lt;p&gt;I wanted SPA-like speed without building a separate API. Inertia lets me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Laravel routing and controllers&lt;/li&gt;
&lt;li&gt;Return React components instead of Blade views&lt;/li&gt;
&lt;li&gt;Get client-side navigation without managing API endpoints&lt;/li&gt;
&lt;li&gt;Keep authentication simple (session-based)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not perfect for every use case, but for a B2B SaaS with authenticated users, it's ideal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Features
&lt;/h2&gt;

&lt;p&gt;Using OpenAI's API (GPT-4o-mini) for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Candidate summaries&lt;/li&gt;
&lt;li&gt;Match scores&lt;/li&gt;
&lt;li&gt;Job description generation&lt;/li&gt;
&lt;li&gt;Interview questions&lt;/li&gt;
&lt;li&gt;Email templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built a credits system so users don't get unlimited API calls. 50,000 credits = ~500 candidate summaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Livewire → React migration was painful but worth it.&lt;/strong&gt; The UI is way more responsive now.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inertia v2's deferred props are game-changing.&lt;/strong&gt; Load the page fast, fetch heavy data after.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI features are table stakes now.&lt;/strong&gt; Users expect them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solo founder = ruthless prioritization.&lt;/strong&gt; I can't build everything.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Just Launched on Product Hunt
&lt;/h2&gt;

&lt;p&gt;If you want to see it in action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PH: &lt;a href="https://www.producthunt.com/posts/jugglehire-2/" rel="noopener noreferrer"&gt;https://www.producthunt.com/posts/jugglehire-2/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LTD: &lt;a href="https://jugglehire.com/lifetime-deal" rel="noopener noreferrer"&gt;https://jugglehire.com/lifetime-deal&lt;/a&gt; (20 spots, $99)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy to answer questions about the stack or the build process.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>recruitment</category>
      <category>startup</category>
      <category>bootstrapping</category>
    </item>
    <item>
      <title>I built a SaaS using Laravel, Livewire &amp; Tailwind CSS</title>
      <dc:creator>Zakir Hossen</dc:creator>
      <pubDate>Tue, 05 Mar 2024 15:34:59 +0000</pubDate>
      <link>https://forem.com/devzakir/i-built-a-saas-using-laravel-livewire-tailwind-css-2h93</link>
      <guid>https://forem.com/devzakir/i-built-a-saas-using-laravel-livewire-tailwind-css-2h93</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the fast-paced world of modern business, finding and managing talent efficiently is a challenge that many entrepreneurs face. This is where JuggleHire, our innovative SaaS platform, steps in to revolutionize the hiring process. JuggleHire is a comprehensive solution designed by the visionary team at Templatecookie and Lomeyo, spearheaded by CEO &amp;amp; Founder Zakir from Dhaka, Bangladesh.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is JuggleHire?
&lt;/h2&gt;

&lt;p&gt;JuggleHire is a cutting-edge SaaS (Software as a Service) platform tailored to streamline and simplify the hiring process for businesses of all sizes. From talent acquisition to collaboration, JuggleHire is a one-stop solution that empowers entrepreneurs and hiring managers to build their dream teams efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack Overview
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend Framework - Laravel v10:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel has been the backbone of JuggleHire, providing a robust and elegant framework for our SaaS application.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frontend Framework - Livewire v3, Alpine.js, and Tailwind CSS &amp;amp; Tailwind UI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The combination of Livewire, Alpine.js, and Tailwind CSS &amp;amp; Tailwind UI allows for a seamless and visually appealing user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database - MySQL:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MySQL serves as our reliable database management system, ensuring data integrity and optimal performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Server Management - DigitalOcean:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DigitalOcean is our preferred platform for server management, offering scalability and reliability in the cloud.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Web Server - Caddy Server:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caddy Server handles sub-domain routing and automatic SSL generation, enhancing the security and performance of JuggleHire.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Text Editor - Trix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trix provides a user-friendly and feature-rich text editing experience within JuggleHire.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Charting - Chart.js:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chart.js enables dynamic and visually appealing charts, enhancing data visualization for better decision-making.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Analytics - Posthog:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posthog plays a pivotal role in tracking user behavior, providing valuable insights for continuous improvement.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fonts - Google Font:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Font contributes to the aesthetic appeal of JuggleHire, ensuring visually pleasing typography.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Additional JS Libraries:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moment.js for efficient date and time handling.&lt;/li&gt;
&lt;li&gt;Choices.js for select functionality.&lt;/li&gt;
&lt;li&gt;Tippy.js for tooltip integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Supporting Tools and Services
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customer Support and Chatbot - Clickconnector:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clickconnector streamlines customer support and engagement, providing a seamless chatbot experience.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Roadmap Software - ProductLift.dev:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ProductLift.dev guides us through the product development roadmap with effective planning and collaboration.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Video Tutorial - Loom:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loom empowers us to create engaging video tutorials, facilitating a better understanding of JuggleHire's features.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Email Marketing - Loops.so:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loops.so executes targeted email marketing campaigns, fostering user engagement and retention.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Transaction Email - Postmark:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postmark ensures reliable delivery of transactional emails, enhancing communication efficiency within JuggleHire.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Caching - Redis:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis provides robust caching mechanisms, optimizing the performance and responsiveness of JuggleHire.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Design and Collaboration - Figma &amp;amp; Figjam:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Figma &amp;amp; Figjam serve as our virtual design studio, promoting collaboration and creativity among the design and development teams.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;JuggleHire SaaS stands at the forefront of innovation, redefining the hiring landscape with a powerful tech stack and a user-centric approach. As we continue to evolve and enhance our platform, our commitment to revolutionizing the hiring experience remains steadfast. To learn more about JuggleHire, &lt;a href="https://jugglehire.com/"&gt;Visit JuggleHire&lt;/a&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>laravel</category>
      <category>tailwindcss</category>
      <category>php</category>
    </item>
  </channel>
</rss>
