<?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: Ujjwal Dubey</title>
    <description>The latest articles on DEV Community by Ujjwal Dubey (@ujjwal_dubey_9).</description>
    <link>https://dev.to/ujjwal_dubey_9</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1739221%2F7066c132-9a19-4d24-846f-cee9b6f64d0d.png</url>
      <title>DEV Community: Ujjwal Dubey</title>
      <link>https://dev.to/ujjwal_dubey_9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ujjwal_dubey_9"/>
    <language>en</language>
    <item>
      <title>Why Brand Audit Scores Disagree Between Tools, and What the Disagreement Tells You</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:59:00 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/why-brand-audit-scores-disagree-between-tools-and-what-the-disagreement-tells-you-3ji3</link>
      <guid>https://dev.to/ujjwal_dubey_9/why-brand-audit-scores-disagree-between-tools-and-what-the-disagreement-tells-you-3ji3</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, which makes a free brand checker. I will treat our tool like any other here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Run the same homepage through three free brand audit tools and you will often get three different verdicts. One says your tone is "professional", another says "inconsistent", a third gives a middling score. Founders take this as proof the tools are random. It is more useful to treat the disagreement as data, including when one of them is our own &lt;a href="https://www.nxflowai.com/war-room/vibe-checker" rel="noopener noreferrer"&gt;free AI website brand audit tool built around tone and claims&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: they read different text
&lt;/h2&gt;

&lt;p&gt;Some tools fetch raw HTML. Others render JavaScript. Some read only the first screen; some read the whole page; some follow links. On a client-rendered site, a raw-HTML tool may see little more than navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it tells you:&lt;/strong&gt; if a raw-HTML tool and a rendering tool disagree sharply, your important copy may not be in the server-rendered HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: they score different things
&lt;/h2&gt;

&lt;p&gt;"Brand" means different things to different tools: visual consistency, tone of voice, claim strength, keyword focus, accessibility. A tool focused on visuals can rate a page highly while a tone-focused tool flags it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it tells you:&lt;/strong&gt; read the categories, not the total.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 3: they compare against different norms
&lt;/h2&gt;

&lt;p&gt;Many tools compare your page with a model of "good websites". If your industry is formal and the model expects casual, you will be marked down for doing the right thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it tells you:&lt;/strong&gt; a low score in one category may be a deliberate choice. Note it and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 4: language models vary
&lt;/h2&gt;

&lt;p&gt;Tools that use language models may give slightly different answers on each run, even for the same page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it tells you:&lt;/strong&gt; run twice. Trust findings that repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical method
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick two or three tools, including at least one that renders JavaScript.&lt;/li&gt;
&lt;li&gt;Run each twice on the same page.&lt;/li&gt;
&lt;li&gt;List findings that appear in at least two tools or both runs.&lt;/li&gt;
&lt;li&gt;Ignore one-off findings unless a human reader agrees.&lt;/li&gt;
&lt;li&gt;Fix the repeated ones, then rerun.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What disagreement cannot tell you
&lt;/h2&gt;

&lt;p&gt;Whether customers like your brand. No tool measures that. Use audits to catch inconsistency and unclear promises, then ask real customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on our own tool
&lt;/h2&gt;

&lt;p&gt;I treat Vibe Checker's output the same way as any other tool's: run it twice and trust the findings that repeat. It sits alongside other free checks in the NxFlowAI War Room, and none of them should be read alone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure again: I co-founded NxFlowAI. Use whichever tools help; the method above works with any of them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>branding</category>
      <category>seo</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Shared Phones and Shared Logins: Access Control for Small Teams Running WhatsApp Automation</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:55:31 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/shared-phones-and-shared-logins-access-control-for-small-teams-running-whatsapp-automation-3e6n</link>
      <guid>https://dev.to/ujjwal_dubey_9/shared-phones-and-shared-logins-access-control-for-small-teams-running-whatsapp-automation-3e6n</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, an AI automation agency in Mumbai. The patterns below are generic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In many small Mumbai businesses, one phone sits on the counter and everyone uses it. The WhatsApp account, the CRM login and the payment app are all on it. That is convenient, and it is the biggest risk when you add automation, because automated flows inherit whatever access that phone has.&lt;/p&gt;

&lt;p&gt;When I think about what makes the &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;best AI automation agency in Mumbai for small businesses with shared devices&lt;/a&gt;, access design is near the top of the list. Here is a practical approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Separate the human inbox from the automation account
&lt;/h2&gt;

&lt;p&gt;Automations should run under their own service account or API credentials, not the shared phone's personal session. If the phone is lost or a staff member leaves, you revoke one thing and the automation keeps running.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Give people individual logins where the tool allows
&lt;/h2&gt;

&lt;p&gt;Most CRMs and shared-inbox tools support multiple users cheaply. Individual logins mean you know who changed a reply template or closed a lead. "Everyone uses the owner's login" removes that history.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use roles, even simple ones
&lt;/h2&gt;

&lt;p&gt;Three roles cover most small teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner:&lt;/strong&gt; can change automations, see everything, manage access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staff:&lt;/strong&gt; can reply, update lead status, cannot change automated messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewer:&lt;/strong&gt; can see reports only, for an accountant or family member.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Keep secrets off the shared phone
&lt;/h2&gt;

&lt;p&gt;API keys and webhook secrets belong in the hosting environment, not in a notes app on the counter phone. If a vendor asks you to paste a key into WhatsApp, refuse and ask for a secure method.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Log changes to automated messages
&lt;/h2&gt;

&lt;p&gt;When someone edits the template that sends payment links, record who, when and what changed. A simple append-only sheet is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-xx-xx 18:02 | owner | payment_link_template | changed greeting line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Plan for staff leaving
&lt;/h2&gt;

&lt;p&gt;Write a short offboarding list: remove their user, change shared passwords they knew, check that no automation was running under their account. Do it the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Review access every quarter
&lt;/h2&gt;

&lt;p&gt;Ten minutes, once a quarter: who has access to what, and should they still?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agencies should raise this
&lt;/h2&gt;

&lt;p&gt;Small business owners rarely ask about access control because nothing has gone wrong yet. A responsible vendor brings it up, because the automations they build make a leaked login more damaging, not less. If a proposal says nothing about accounts, roles or offboarding, ask.&lt;/p&gt;

&lt;p&gt;For a wider view of what to check before any build, a workflow audit with a risk log is a good start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure again: I co-founded NxFlowAI. None of the above requires our services.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>whatsapp</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A Business-Calendar Service for Indian Automation Flows: Working Hours, Holidays and Regional Festivals</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:50:38 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/a-business-calendar-service-for-indian-automation-flows-working-hours-holidays-and-regional-3gi7</link>
      <guid>https://dev.to/ujjwal_dubey_9/a-business-calendar-service-for-indian-automation-flows-working-hours-holidays-and-regional-3gi7</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, a Mumbai-based AI automation agency. The pattern below is generic and is not a product pitch.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Automations that send reminders, escalate unanswered leads or promise "we will reply by tomorrow" all depend on one thing: knowing when the business is actually open. In India that is harder than it looks. Working days differ by business, weekly offs differ by city, and festivals differ by state and community. Any &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;AI automation agency India SMEs rely on for reminders&lt;/a&gt; needs a calendar layer instead of hard-coded hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with hard-coded hours
&lt;/h2&gt;

&lt;p&gt;The usual first version is &lt;code&gt;if 10 &amp;lt;= hour &amp;lt; 19 and weekday &amp;lt; 6&lt;/code&gt;. It breaks immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A shop in one city closes on Monday; a clinic elsewhere closes on Thursday afternoon.&lt;/li&gt;
&lt;li&gt;Diwali, Eid, Onam, Pongal, Durga Puja and Ganeshotsav each matter to different businesses in different regions.&lt;/li&gt;
&lt;li&gt;Owners close unexpectedly for a family function and tell customers on WhatsApp, not in any system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the code does not know about these, customers get "we will call you in an hour" at 9 pm on a festival night.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny calendar service
&lt;/h2&gt;

&lt;p&gt;Keep one small service that answers three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;is_open(business_id, datetime)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next_open(business_id, datetime)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_business_minutes(business_id, datetime, minutes)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every flow calls it instead of checking hours itself.&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;business&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-store&lt;/span&gt;
&lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Asia/Kolkata&lt;/span&gt;
&lt;span class="na"&gt;weekly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;closed&lt;/span&gt;
  &lt;span class="na"&gt;tue-sat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10:30-20:00"&lt;/span&gt;
  &lt;span class="na"&gt;sun&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11:00-14:00"&lt;/span&gt;
&lt;span class="na"&gt;holidays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner_list&lt;/span&gt;   &lt;span class="c1"&gt;# owner picks from a regional list, then edits&lt;/span&gt;
  &lt;span class="na"&gt;dates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;ad_hoc_closures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# set from a WhatsApp command by the owner&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let the owner close the shop from WhatsApp
&lt;/h2&gt;

&lt;p&gt;The most useful feature is a single command the owner can send, such as "CLOSED TODAY". The service records an ad-hoc closure, and every pending reminder shifts to the next open slot. Without this, the calendar goes stale within a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regional festival lists as suggestions, not truth
&lt;/h2&gt;

&lt;p&gt;Ship a starting list per state, but make the owner confirm it. A jeweller and a hardware store on the same street may treat the same festival very differently: one is at peak, one is shut.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use business minutes for promises
&lt;/h2&gt;

&lt;p&gt;If a flow promises "reply within 2 business hours", compute the deadline with &lt;code&gt;add_business_minutes&lt;/code&gt;. Escalations then fire at a sensible time, not at midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test with ugly dates
&lt;/h2&gt;

&lt;p&gt;Write tests for the day before a long weekend, a closure announced at 9 am, and a reminder scheduled across a festival. These are where real complaints come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;Calendar logic is unglamorous, but it decides whether automation feels polite or robotic. If you are mapping which of your flows depend on timing, list them before you build. A 72-hour workflow audit is one way to do that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure again: I co-founded NxFlowAI. The pattern above is generic and works with any stack.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>automation</category>
      <category>india</category>
      <category>python</category>
    </item>
    <item>
      <title>Routing Mumbai Service Enquiries by Pincode: A Small Lookup That Sends Each Lead to the Right Person</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:45:17 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/routing-mumbai-service-enquiries-by-pincode-a-small-lookup-that-sends-each-lead-to-the-right-person-1p0o</link>
      <guid>https://dev.to/ujjwal_dubey_9/routing-mumbai-service-enquiries-by-pincode-a-small-lookup-that-sends-each-lead-to-the-right-person-1p0o</guid>
      <description>&lt;p&gt;Small service businesses in Mumbai (AC repair, pest control, tutors, caterers) often have staff who cover specific areas. When an enquiry arrives, someone has to work out who should take it. A pincode lookup removes that step, and it is a typical first build when you look for the &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;best AI automation agency in Mumbai for small businesses with field staff&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data: one sheet the owner controls
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pincode&lt;/th&gt;
&lt;th&gt;area_label&lt;/th&gt;
&lt;th&gt;owner&lt;/th&gt;
&lt;th&gt;backup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4000XX&lt;/td&gt;
&lt;td&gt;Area A&lt;/td&gt;
&lt;td&gt;Ravi&lt;/td&gt;
&lt;td&gt;Sana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4000YY&lt;/td&gt;
&lt;td&gt;Area B&lt;/td&gt;
&lt;td&gt;Sana&lt;/td&gt;
&lt;td&gt;Ravi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep it in Google Sheets so the owner can edit it without a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lookup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;PIN_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b4\d{5}\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Mumbai-region pincodes start with 4
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_owner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PIN_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;default_owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_pincode_ask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;default_owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_pincode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matched&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When there is no pincode
&lt;/h2&gt;

&lt;p&gt;Ask once, politely: "Could you share your area pincode so the right person can help?" If the customer replies with an area name instead, send it to the default owner. Do not guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backups and leave
&lt;/h2&gt;

&lt;p&gt;If the owner of an area is on leave (mark it in the sheet), route to the backup. Missing this is how leads sit unanswered for a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log every decision
&lt;/h2&gt;

&lt;p&gt;Write the routing reason next to each lead. When a customer complains, you can see why it went where it did.&lt;/p&gt;

&lt;p&gt;This is small, boring and effective. If you want help deciding what to automate first, start with a workflow audit before you buy anything.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, a Mumbai-based AI automation agency.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>india</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A QR Check-In for Showroom Appointments: Closing the Loop Between a WhatsApp Booking and the Walk-In</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:40:34 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/a-qr-check-in-for-showroom-appointments-closing-the-loop-between-a-whatsapp-booking-and-the-walk-in-2g2o</link>
      <guid>https://dev.to/ujjwal_dubey_9/a-qr-check-in-for-showroom-appointments-closing-the-loop-between-a-whatsapp-booking-and-the-walk-in-2g2o</guid>
      <description>&lt;p&gt;Jewellery showrooms that book appointments on WhatsApp usually lose track at the door: the customer walks in, nobody connects them to the booking, and the salesperson who prepared is busy elsewhere. A tiny QR check-in fixes that, and it is a typical small piece of &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;AI automation for jewellery retail India showrooms ask about first&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Booking confirmed on WhatsApp; the message includes a QR code with a short signed token.&lt;/li&gt;
&lt;li&gt;At the showroom, the receptionist scans it on a tablet.&lt;/li&gt;
&lt;li&gt;The assigned salesperson gets a WhatsApp alert with the customer's noted preferences.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Signed tokens
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rotate-me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# keep in env, never in code
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;booking_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_age_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_age_days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the image with any QR library (for example &lt;code&gt;qrcode&lt;/code&gt;) from the token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep personal data out of the QR
&lt;/h2&gt;

&lt;p&gt;The token carries a booking id only. Preferences are looked up on scan, inside your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle no-shows politely
&lt;/h2&gt;

&lt;p&gt;If nobody scans within the slot, send the salesperson a prompt to follow up personally. Do not auto-send "you missed your appointment".&lt;/p&gt;

&lt;h2&gt;
  
  
  Walk-ins without a QR
&lt;/h2&gt;

&lt;p&gt;The receptionist searches by phone number. The QR is a convenience, not a gate.&lt;/p&gt;

&lt;p&gt;Before you build anything like this, map the current visit flow first, so the QR step solves a real gap.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, a Mumbai-based AI automation agency.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>whatsapp</category>
      <category>qrcode</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>Find Where Work Waits: Computing Dwell Time Between Workflow Steps From a Plain CSV Export</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:36:27 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/find-where-work-waits-computing-dwell-time-between-workflow-steps-from-a-plain-csv-export-1p6j</link>
      <guid>https://dev.to/ujjwal_dubey_9/find-where-work-waits-computing-dwell-time-between-workflow-steps-from-a-plain-csv-export-1p6j</guid>
      <description>&lt;p&gt;Before buying any AI tool, it helps to know where work actually waits. Most CRMs, helpdesks and form tools can export a CSV with a record ID, a status and a timestamp. That is enough to &lt;a href="https://www.nxflowai.com/blog/how-to-audit-business-workflow-before-ai" rel="noopener noreferrer"&gt;audit a business workflow for waiting time&lt;/a&gt; without new software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The input
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record_id,status,changed_at
101,new,2026-10-01T09:02
101,contacted,2026-10-01T15:40
101,quoted,2026-10-03T11:15
102,new,2026-10-01T10:10
102,contacted,2026-10-02T09:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;median&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_log.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;record_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;changed_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="n"&gt;waits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
        &lt;span class="n"&gt;waits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;waits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; median &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; h  (n=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading the output
&lt;/h2&gt;

&lt;p&gt;The transition with the largest median wait is usually a handoff: someone has to notice, decide or copy something. That is where automation helps most, and often a reminder or a routing rule is enough before any AI is involved.&lt;/p&gt;

&lt;p&gt;Two cautions: status changes are only logged when someone updates the record, so long waits can also mean "forgot to update." Check a few records by hand.&lt;/p&gt;

&lt;p&gt;This works as a warm-up before a fuller workflow audit. (Disclosure: I co-founded NxFlowAI, whose first audit takes 72 hours.)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NxFlowAI is not affiliated with NexFlow AI or Nexusflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>data</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What the Reviewer Should See: Designing the Approval Card for AI Actions</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:14:05 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/what-the-reviewer-should-see-designing-the-approval-card-for-ai-actions-49hf</link>
      <guid>https://dev.to/ujjwal_dubey_9/what-the-reviewer-should-see-designing-the-approval-card-for-ai-actions-49hf</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, which builds AI workflows with approval steps. Examples are generic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Frameworks now make it easy to pause an agent and wait for a person. They say much less about what that person sees. Yet the quality of &lt;a href="https://www.nxflowai.com/blog/custom-ai-vs-off-the-shelf-chatbots" rel="noopener noreferrer"&gt;human-in-the-loop approval in production AI workflows&lt;/a&gt; depends almost entirely on the approval card.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimum card
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The trigger.&lt;/strong&gt; The customer message or event that started this, shown in full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proposed action, in plain words.&lt;/strong&gt; "Send this reply and issue a refund for order 1182," not a JSON blob.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The draft itself&lt;/strong&gt;, editable in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it stopped.&lt;/strong&gt; "Refunds always need approval" or "low confidence: customer mentions a complaint."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data used.&lt;/strong&gt; Order status, past messages, the policy the AI relied on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three actions:&lt;/strong&gt; approve, edit, reject (with a one-word reason).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Helpful extras
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customer history at a glance:&lt;/strong&gt; previous complaints, open deals, VIP flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time waiting&lt;/strong&gt;, so old items are visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What changed&lt;/strong&gt; if the item was sent back because the situation moved on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to leave out
&lt;/h2&gt;

&lt;p&gt;Model names, token counts and confidence scores to several decimals. Reviewers are business people. Show them business context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it with a stopwatch
&lt;/h2&gt;

&lt;p&gt;Ask a reviewer to process ten real items. If any item takes longer than doing the task by hand, the card is missing context or showing too much.&lt;/p&gt;

&lt;p&gt;If you are planning a first build, our services overview of audit, build and monitor explains how we approach it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure again: I have a commercial interest in NxFlowAI.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>workflow</category>
      <category>automation</category>
    </item>
    <item>
      <title>Who Owns the Phone Number Field? Field Ownership Rules That Stop WhatsApp to CRM Sync Loops</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:10:49 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/who-owns-the-phone-number-field-field-ownership-rules-that-stop-whatsapp-to-crm-sync-loops-5c97</link>
      <guid>https://dev.to/ujjwal_dubey_9/who-owns-the-phone-number-field-field-ownership-rules-that-stop-whatsapp-to-crm-sync-loops-5c97</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, which builds WhatsApp and CRM integrations. Examples are generic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Two-way sync" sounds like a feature. In practice it is where most WhatsApp and CRM integrations start fighting themselves. The fix is a decision you make before writing any code, the first one to make when you &lt;a href="https://www.nxflowai.com/blog/how-to-audit-business-workflow-before-ai" rel="noopener noreferrer"&gt;scope a WhatsApp Business API to CRM integration&lt;/a&gt;: for every field, which system owns it?&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A rep edits a contact name in the CRM.&lt;/li&gt;
&lt;li&gt;The sync pushes it to the WhatsApp provider's contact list.&lt;/li&gt;
&lt;li&gt;The provider's webhook fires "contact updated."&lt;/li&gt;
&lt;li&gt;The integration writes the provider's version back to the CRM, sometimes with a slightly different format.&lt;/li&gt;
&lt;li&gt;The CRM fires its own update event. Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;Each field has exactly one owner. Other systems may read it; only the owner writes it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Usual owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phone number (E.164)&lt;/td&gt;
&lt;td&gt;CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display name&lt;/td&gt;
&lt;td&gt;CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opt-in and opt-out&lt;/td&gt;
&lt;td&gt;CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last inbound message time&lt;/td&gt;
&lt;td&gt;Messaging side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversation transcript&lt;/td&gt;
&lt;td&gt;Messaging side, copied as activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deal stage&lt;/td&gt;
&lt;td&gt;CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tag writes with their origin&lt;/strong&gt; and ignore events your own integration caused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare before writing.&lt;/strong&gt; If the normalised value is unchanged, do nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every write with the field and the reason.&lt;/strong&gt; Debugging a loop without this is guesswork.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on tools
&lt;/h2&gt;

&lt;p&gt;We integrate with all major CRMs (for example HubSpot, Zoho, Salesforce, Pipedrive and Freshsales, plus custom CRMs or Google Sheets setups) and work with all major WhatsApp Business providers and the official WhatsApp Business API (for example Meta Cloud API, Twilio, Gupshup, Interakt and WATI). Whichever pair you use, the ownership table above applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters for AI
&lt;/h2&gt;

&lt;p&gt;When an AI step drafts replies or updates stages, unclear ownership multiplies. Decide ownership first and the AI step has clear rules about what it may touch.&lt;/p&gt;

&lt;p&gt;The free recon tools we publish for founders are lighter if you want to see our approach.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure again: I have a commercial interest in NxFlowAI.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>whatsapp</category>
      <category>crm</category>
      <category>api</category>
      <category>automation</category>
    </item>
    <item>
      <title>Shared Inboxes, Many Hands: Ownership Locks for Small-Team Email Automation</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:07:30 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/shared-inboxes-many-hands-ownership-locks-for-small-team-email-automation-55ba</link>
      <guid>https://dev.to/ujjwal_dubey_9/shared-inboxes-many-hands-ownership-locks-for-small-team-email-automation-55ba</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, an AI automation agency. Examples are generic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Small UK firms often run one shared inbox: info@, hello@, &lt;a href="mailto:enquiries@"&gt;enquiries@&lt;/a&gt;. Three people read it, and now an automation drafts replies too. The classic failure is the double reply: two staff answer the same customer with different prices, or a person answers while the bot's draft also goes out.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;AI automation agency builds for UK small-business inboxes&lt;/a&gt;, the pattern I recommend is an ownership lock. Here is the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, stated precisely
&lt;/h2&gt;

&lt;p&gt;A thread can be touched by several actors: humans in the mail client, an automation drafting replies, and sometimes a CRM sequence. None of them knows the others are working. You need a shared notion of "who owns this thread right now".&lt;/p&gt;

&lt;h2&gt;
  
  
  The lock
&lt;/h2&gt;

&lt;p&gt;Store one record per thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;thread&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;person&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"automation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;claimed&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;expires&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claim before acting.&lt;/strong&gt; Any actor must claim the thread before drafting or sending. Claims are atomic (a conditional update that only succeeds if the thread is unowned or the claim has expired).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Humans outrank automation.&lt;/strong&gt; A person can always take a thread from the automation. The automation can never take a thread from a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locks expire.&lt;/strong&gt; If a person claims a thread and goes to lunch, the claim expires and the thread returns to the queue with a nudge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release on send.&lt;/strong&gt; Sending a reply releases the lock and records who replied.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Surfacing the lock to people
&lt;/h2&gt;

&lt;p&gt;The lock is useless if people cannot see it. Put the owner's name in a label or category inside the mail client, or a short note at the top of the thread. People will respect a lock they can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drafts, not sends
&lt;/h2&gt;

&lt;p&gt;For most small firms, the automation should draft and a person should send. That alone removes most double-reply risk. Where the automation does send (acknowledgements, confirmations), it must hold the lock first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test with chaos
&lt;/h2&gt;

&lt;p&gt;Simulate two people and the automation working the same queue. If you never see a double reply in testing, the design is probably right.&lt;/p&gt;

&lt;p&gt;For a broader view of mapping who touches what before building, see our notes on auditing a workflow before AI.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure, again: I co-founded NxFlowAI and have a commercial interest in this topic.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>email</category>
      <category>automation</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Stale Approvals: Re-Check the Draft Before You Execute It</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:04:15 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/stale-approvals-re-check-the-draft-before-you-execute-it-1g0</link>
      <guid>https://dev.to/ujjwal_dubey_9/stale-approvals-re-check-the-draft-before-you-execute-it-1g0</guid>
      <description>&lt;p&gt;A reviewer approves an AI-drafted refund at 4 pm. The workflow executes it at 4:05. Between those times, the customer wrote "never mind, found it." Who is right? This is the stale approval problem, and it shows up in almost every &lt;a href="https://www.nxflowai.com/blog/custom-ai-vs-off-the-shelf-chatbots" rel="noopener noreferrer"&gt;human-in-the-loop approval step in an AI workflow&lt;/a&gt; once volume grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pin the approval to a version
&lt;/h2&gt;

&lt;p&gt;Approve a specific snapshot, not "the refund for customer X."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_msg_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_msg_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_approval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Re-check right before executing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_if_still_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not_approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;load_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;needs_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stale_back_to_reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choose fingerprint fields carefully.&lt;/strong&gt; Include what would change the decision (new messages, order status, amount), not every timestamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use an idempotency key&lt;/strong&gt; so a retry does not refund twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell the reviewer why it came back.&lt;/strong&gt; "Customer sent a new message after approval" saves a confused second look.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We write about scoping these decisions in our guide to auditing a workflow first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Validating ABNs, Australian Phone Numbers and Addresses in Intake Automations</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:01:00 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/validating-abns-australian-phone-numbers-and-addresses-in-intake-automations-17kd</link>
      <guid>https://dev.to/ujjwal_dubey_9/validating-abns-australian-phone-numbers-and-addresses-in-intake-automations-17kd</guid>
      <description>&lt;p&gt;If you build intake or invoicing automations for Australian businesses, three fields cause most of the bad data: the ABN, the phone number and the address. Validating them at the edge saves a lot of manual clean-up later.&lt;/p&gt;

&lt;p&gt;These are checks worth adding to any &lt;a href="https://www.nxflowai.com/services" rel="noopener noreferrer"&gt;AI automation agency builds for Australian businesses&lt;/a&gt; project. (Disclosure: I co-founded NxFlowAI, a Mumbai-based agency that works remotely.)&lt;/p&gt;

&lt;h2&gt;
  
  
  ABN: check the checksum before you look it up
&lt;/h2&gt;

&lt;p&gt;An Australian Business Number has 11 digits and a published checksum. Subtract 1 from the first digit, multiply each digit by its weight, sum, and the total must divide evenly by 89.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ABN_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;abn_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;digits&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="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ABN_WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;89&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A valid checksum only means the number is well formed. If the business status matters (for example, before invoicing), look it up through the official ABN Lookup service and check its terms of use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phones: store +61 in E.164
&lt;/h2&gt;

&lt;p&gt;Australian numbers arrive as &lt;code&gt;0412 345 678&lt;/code&gt;, &lt;code&gt;+61 412 345 678&lt;/code&gt; or &lt;code&gt;61412345678&lt;/code&gt;. Normalise once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;au_phone_e164&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;61&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+61&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anything beyond simple cases, use a maintained library such as &lt;code&gt;phonenumbers&lt;/code&gt; with region &lt;code&gt;AU&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addresses: state and postcode
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Store state as one of NSW, VIC, QLD, WA, SA, TAS, ACT, NT.&lt;/li&gt;
&lt;li&gt;Store postcode as a four-character string.&lt;/li&gt;
&lt;li&gt;Keep the original full address for whoever drives there.&lt;/li&gt;
&lt;li&gt;If you add a validation service, read its terms and pricing yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it runs
&lt;/h2&gt;

&lt;p&gt;Validate in the form or webhook handler. If a field fails, do not reject the enquiry; flag it for a person and continue. A lost lead is worse than a messy one.&lt;/p&gt;

&lt;p&gt;The step before code is knowing which fields the business really needs; our field-by-field workflow mapping notes cover that.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>australia</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shadow AI in Small Businesses: What a Workflow Audit Finds When Staff Use Personal Chatbots</title>
      <dc:creator>Ujjwal Dubey</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:01:19 +0000</pubDate>
      <link>https://dev.to/ujjwal_dubey_9/shadow-ai-in-small-businesses-what-a-workflow-audit-finds-when-staff-use-personal-chatbots-303d</link>
      <guid>https://dev.to/ujjwal_dubey_9/shadow-ai-in-small-businesses-what-a-workflow-audit-finds-when-staff-use-personal-chatbots-303d</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I co-founded NxFlowAI, an AI automation agency that runs workflow audits for small businesses. I have a commercial interest in audits being taken seriously.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you read through a small company's real workflows, one finding is easy to miss because it is rarely on anyone's list. Staff are already using AI. Not the company's AI. Their own: a personal chatbot account, a browser extension, a free transcription app. A useful &lt;a href="https://www.nxflowai.com/blog/how-to-audit-business-workflow-before-ai" rel="noopener noreferrer"&gt;workflow audit that surfaces shadow AI&lt;/a&gt; treats this as information, not a scandal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;People are trying to get through the day. A personal chatbot writes a polite reply to a difficult customer faster than they can. A transcription app saves them typing up a call. Nobody asked permission because nobody thought it needed asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Customer messages pasted into a personal chatbot to draft replies.&lt;/li&gt;
&lt;li&gt;Supplier quotes summarised by a free tool.&lt;/li&gt;
&lt;li&gt;Meeting recordings uploaded to transcription services.&lt;/li&gt;
&lt;li&gt;Browser extensions with permission to read every page, including the CRM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real risks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data in places you did not choose.&lt;/strong&gt; Customer details, prices and internal notes end up in services under personal accounts, under terms the business never read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inconsistency.&lt;/strong&gt; Five people using five tools produce five tones and five levels of accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invisible dependency.&lt;/strong&gt; A workflow quietly relies on one person's personal subscription. When they leave, the workflow degrades and nobody knows why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No review.&lt;/strong&gt; AI-drafted messages go to customers without anyone else seeing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to surface it without blame
&lt;/h2&gt;

&lt;p&gt;Ask the question plainly in interviews: "Do you use any AI tools to help with this, including free or personal ones?" Make it clear there is no penalty. People tend to answer honestly when they believe it, and the answers are useful: they show exactly where staff felt the most pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with the findings
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treat each use as a requirement.&lt;/strong&gt; If someone uses a chatbot to draft replies, that is evidence the business needs drafting help, in a tool it controls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick sanctioned tools&lt;/strong&gt; under business accounts, with terms someone has read, and check any obligations that apply where you operate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a short usage note:&lt;/strong&gt; what can be pasted into which tool, and what never can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add review where it matters.&lt;/strong&gt; AI-drafted customer messages about money or commitments get a second look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revisit periodically.&lt;/strong&gt; New tools appear constantly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The opportunity
&lt;/h2&gt;

&lt;p&gt;Shadow AI is a map of demand. The places where staff reached for AI on their own are usually the best candidates for a proper, reviewed workflow. Handled well, the finding turns into the roadmap.&lt;/p&gt;

&lt;p&gt;For the build side of that roadmap, see how we automate lead-to-close work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure (again): I am Ujjwal Dubey, co-founder of NxFlowAI (nxflowai.com), a Mumbai-based AI automation agency working with businesses worldwide remotely. Not to be confused with NexFlow or Nexusflow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>smallbusiness</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
