<?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: Azeem Javed</title>
    <description>The latest articles on DEV Community by Azeem Javed (@jeemmo).</description>
    <link>https://dev.to/jeemmo</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%2F1094809%2F3da06d21-15cd-4488-8a7b-507b4aa18505.png</url>
      <title>DEV Community: Azeem Javed</title>
      <link>https://dev.to/jeemmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeemmo"/>
    <language>en</language>
    <item>
      <title>My SMTP relay broke DKIM twice. Here's how I found both bugs.</title>
      <dc:creator>Azeem Javed</dc:creator>
      <pubDate>Thu, 29 Oct 2026 23:01:15 +0000</pubDate>
      <link>https://dev.to/jeemmo/my-smtp-relay-broke-dkim-twice-heres-how-i-found-both-bugs-46g6</link>
      <guid>https://dev.to/jeemmo/my-smtp-relay-broke-dkim-twice-heres-how-i-found-both-bugs-46g6</guid>
      <description>&lt;p&gt;I run several small sites on one Ubuntu VPS. Mail goes out through Postfix, gets signed by OpenDKIM, and then leaves through a shared SMTP relay (Hostinger, which sends onward via MailChannels). I don't send directly from the VPS because a fresh IP has no sending reputation.&lt;/p&gt;

&lt;p&gt;DKIM looked fine on paper. &lt;code&gt;opendkim-testkey&lt;/code&gt; said &lt;code&gt;key OK&lt;/code&gt;. The mail log showed &lt;code&gt;DKIM-Signature field added&lt;/code&gt;. And Google's DMARC reports still said &lt;strong&gt;dkim=fail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It turned out to be two separate bugs, with the same cause: &lt;strong&gt;the relay changes the message after I sign it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: the body changed
&lt;/h2&gt;

&lt;p&gt;The first failure came from website contact forms. Their emails had long lines and were sent as &lt;code&gt;8bit&lt;/code&gt;. On the way out, the relay re-wrapped those long lines. The body changed, so the body hash (&lt;code&gt;bh=&lt;/code&gt;) no longer matched, so DKIM failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; send the body as quoted-printable. QP lines are already short (76 characters), so the relay has nothing to re-wrap. In PHP:&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="nv"&gt;$qpBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;quoted_printable_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// header: Content-Transfer-Encoding: quoted-printable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, some domains passed. One still failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: a header changed
&lt;/h2&gt;

&lt;p&gt;This one was harder, because the body hash was now correct. In Gmail's &lt;strong&gt;Show original&lt;/strong&gt;, the &lt;code&gt;bh=&lt;/code&gt; in my DKIM signature matched the &lt;code&gt;bh=&lt;/code&gt; in Google's own ARC signature. So the body arrived untouched, and the problem had to be a signed &lt;strong&gt;header&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My signature had &lt;code&gt;h=From:To:Subject:Date:From&lt;/code&gt;. I compared the headers I sent with the ones Gmail received:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sent:&lt;/strong&gt; &lt;code&gt;Date: Thu, 29 Oct 2026 22:17:14 -0000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Received:&lt;/strong&gt; &lt;code&gt;Date: Thu, 29 Oct 2026 22:17:14 +0000 (UTC)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same time, different text. The relay had removed my &lt;code&gt;Date&lt;/code&gt; header and added its own, in its own format. And &lt;code&gt;Date&lt;/code&gt; is a signed header, so one changed character breaks the signature.&lt;/p&gt;

&lt;p&gt;Why only some emails? My website forms (PHP &lt;code&gt;mail()&lt;/code&gt;) never set a Date header. Postfix adds one in exactly the format the relay uses, so the relay's rewrite produced identical text. Only my Python scripts set their own Date with &lt;code&gt;email.utils.formatdate()&lt;/code&gt;, which writes &lt;code&gt;-0000&lt;/code&gt;. That's a valid date, just not the same string.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I proved it
&lt;/h2&gt;

&lt;p&gt;I sent test emails to a Gmail inbox and checked &lt;strong&gt;Show original&lt;/strong&gt; on each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Date header&lt;/th&gt;
&lt;th&gt;DKIM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;own, &lt;code&gt;-0000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;FAIL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;own, &lt;code&gt;+0000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;FAIL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;none (Postfix adds it)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PASS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even a "correct-looking" &lt;code&gt;+0000&lt;/code&gt; failed, because the relay also appends &lt;code&gt;(UTC)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't set a Date header in scripts that send through the relay.&lt;/strong&gt; Let Postfix add it. Postfix writes it in the same format the relay produces, so the signed text survives.&lt;/p&gt;

&lt;p&gt;The other option is to stop signing &lt;code&gt;Date&lt;/code&gt; in OpenDKIM (&lt;code&gt;SignHeaders&lt;/code&gt;). I didn't, because Date is one of the headers the DKIM spec recommends signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd check first next time
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Read the &lt;strong&gt;DMARC aggregate reports&lt;/strong&gt;. They tell you which selector and which source IP failed.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Show original&lt;/strong&gt;, compare your &lt;code&gt;bh=&lt;/code&gt; with the receiver's ARC &lt;code&gt;bh=&lt;/code&gt;. If they match, the body is fine and a &lt;strong&gt;header&lt;/strong&gt; changed.&lt;/li&gt;
&lt;li&gt;Diff the headers you sent against the headers received. Any signed header that differs is your bug.&lt;/li&gt;
&lt;li&gt;Remember that a relay can rewrite both the body and the headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mail still got delivered the whole time, because my DMARC policy was &lt;code&gt;p=none&lt;/code&gt;. But a failing signature is exactly the kind of thing that quietly moves you into spam, and I'd rather find it from a report than from a client who never got my reply.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Azeem. I build and fix web systems (WooCommerce, Stripe, AI features) at &lt;a href="https://jeemmo.com" rel="noopener noreferrer"&gt;jeemmo.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>email</category>
      <category>devops</category>
      <category>linux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I stop an LLM from hallucinating in production (RAG + entity-match + MCP)</title>
      <dc:creator>Azeem Javed</dc:creator>
      <pubDate>Sat, 12 Sep 2026 12:24:40 +0000</pubDate>
      <link>https://dev.to/jeemmo/how-i-stop-an-llm-from-hallucinating-in-production-rag-entity-match-mcp-2ndj</link>
      <guid>https://dev.to/jeemmo/how-i-stop-an-llm-from-hallucinating-in-production-rag-entity-match-mcp-2ndj</guid>
      <description>&lt;p&gt;How do you make an LLM answer questions about real-world entities — companies, people, records — without it confidently making things up?&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://verivello.org" rel="noopener noreferrer"&gt;Verivello&lt;/a&gt;, a live AI agent that answers questions about any UK company from official registers (Companies House, HM Land Registry, FCA, The Gazette, sanctions lists). In that domain, a &lt;em&gt;wrong&lt;/em&gt; director or ownership figure is worse than no answer at all. So "don't hallucinate" isn't a nice-to-have — it's the whole product.&lt;/p&gt;

&lt;p&gt;Here's the system design I use to keep an LLM grounded in production. No prompt-magic, no fine-tuning. Just architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;LLMs are fluent, and fluency reads as confidence. Ask a model "who is the director of Acme Ltd?" and it will happily produce a plausible name — whether or not it actually knows. For casual use that's fine. For anything a business acts on, it's a liability.&lt;/p&gt;

&lt;p&gt;The usual first instinct is "add RAG." Retrieval-augmented generation helps, but on its own it isn't enough: retrieval can pull the &lt;em&gt;wrong&lt;/em&gt; record, and the model can still paraphrase it into something subtly false. Grounding is a pipeline, not a single step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system at a glance
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    U[User question] --&amp;gt; C[Classifier: intent + entity]
    C --&amp;gt; R[Engine router]
    R --&amp;gt; T[Tool / function-calling loop]
    T --&amp;gt; M[MCP tool servers]
    M --&amp;gt; S1[Companies House]
    M --&amp;gt; S2[Land Registry]
    M --&amp;gt; S3[FCA / ICO]
    M --&amp;gt; S4[Gazette / Sanctions]
    S1 &amp;amp; S2 &amp;amp; S3 &amp;amp; S4 --&amp;gt; V[Grounding layer:&amp;lt;br/&amp;gt;verbatim output + entity-match]
    V --&amp;gt; A[Streamed, source-cited answer]
    A --&amp;gt; U&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Two rules in that &lt;strong&gt;grounding layer&lt;/strong&gt; do most of the anti-hallucination work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1 — Verbatim tool output, not model memory
&lt;/h2&gt;

&lt;p&gt;The model is &lt;strong&gt;never&lt;/strong&gt; allowed to recall a fact about an entity. Every fact in an answer must come from a tool call whose raw output is passed through unchanged. The model's job is to &lt;em&gt;explain and cite&lt;/em&gt; retrieved records — not to &lt;em&gt;know&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tools return structured records (JSON), and that JSON is placed in context verbatim.&lt;/li&gt;
&lt;li&gt;The system prompt instructs the model to answer &lt;strong&gt;only&lt;/strong&gt; from provided records and to cite them.&lt;/li&gt;
&lt;li&gt;If a fact isn't in the retrieved records, the model must not supply it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one rule kills the most common failure mode: the model "filling in" a field from training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2 — Entity-match verification
&lt;/h2&gt;

&lt;p&gt;This is the rule most people miss. Name-search APIs happily return &lt;em&gt;a&lt;/em&gt; record for a query — often the wrong one. Search "Smith Consulting" and you might get any of dozens. If you feed that straight into context, you've grounded the model in the wrong company. Now it's confidently wrong &lt;em&gt;with a citation&lt;/em&gt;, which is worse.&lt;/p&gt;

&lt;p&gt;So before any record is allowed into the grounded context, it's re-verified against the entity the user actually asked about. Illustrative shape (not production code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Reject records that don't actually match the entity the user asked about.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Strongest signal: exact identifier match.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyNumber&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;companyNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Otherwise: normalised name + a corroborating signal.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;sameIncorporationYear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Only verified records are allowed into the grounded context.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grounded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toolResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;verifyMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mismatches are dropped, not shown. The model only ever sees records that provably belong to the queried entity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden rule — fail closed
&lt;/h2&gt;

&lt;p&gt;If nothing verifies, the agent says so. &lt;strong&gt;No verified record → an honest "I don't know," never a plausible guess.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a product decision as much as an engineering one. In due diligence, a confident wrong answer is the expensive failure mode — far more costly than an honest gap. So the whole pipeline is built to fail &lt;em&gt;closed&lt;/em&gt;: when in doubt, refuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP for the tool layer
&lt;/h2&gt;

&lt;p&gt;Each data source is exposed as a typed &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP) tool server. That gives a few real benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation &amp;amp; testability&lt;/strong&gt; — each source is an independent server with a typed schema, unit-testable on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse&lt;/strong&gt; — the same tool servers work across clients (Claude Code, Claude Desktop, the product itself).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clean boundary&lt;/strong&gt; — the model requests &lt;em&gt;what&lt;/em&gt; it needs; the tool layer owns &lt;em&gt;how&lt;/em&gt; it's fetched and verified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I open-sourced a small, keyless example of this pattern — an MCP server exposing UK public-data tools, with unit tests + CI — here: &lt;strong&gt;&lt;a href="https://github.com/cavitnation/mcp-uk-tools" rel="noopener noreferrer"&gt;github.com/cavitnation/mcp-uk-tools&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're wiring an LLM up to real data, grounding is a pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verbatim tool output&lt;/strong&gt; — the model explains and cites; it never recalls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity-match verification&lt;/strong&gt; — prove each record belongs to the queried entity before using it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail closed&lt;/strong&gt; — no verified record, no answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP tool servers&lt;/strong&gt; — a clean, testable, reusable boundary between model and data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the difference between a demo and something a business can actually trust to act.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I wrote up the full architecture (with the diagram, no product code) here: &lt;a href="https://github.com/cavitnation/verivello-architecture" rel="noopener noreferrer"&gt;github.com/cavitnation/verivello-architecture&lt;/a&gt;. I build production AI and backend systems like this for clients at &lt;a href="https://jeemmo.com/case-studies/" rel="noopener noreferrer"&gt;jeemmo.com&lt;/a&gt; — happy to talk shop in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
      <category>llm</category>
    </item>
    <item>
      <title>Hi DEV — I build backend-heavy web apps</title>
      <dc:creator>Azeem Javed</dc:creator>
      <pubDate>Tue, 08 Sep 2026 10:38:02 +0000</pubDate>
      <link>https://dev.to/jeemmo/hi-dev-i-build-backend-heavy-web-apps-16ji</link>
      <guid>https://dev.to/jeemmo/hi-dev-i-build-backend-heavy-web-apps-16ji</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5l4tqzkntjsi0srb89t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5l4tqzkntjsi0srb89t.png" alt="Jeemmo — backend-heavy web app development" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm Azeem — I build backend-heavy web apps solo as &lt;a href="https://jeemmo.com/" rel="noopener noreferrer"&gt;Jeemmo&lt;/a&gt;, since 2011, for UK / EU / US clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I do:&lt;/strong&gt; custom web apps, real-time features (WebSockets), Stripe / payment integrations, rescuing old PHP, and making slow WooCommerce stores fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; PHP / Laravel, Node.js, Python, MySQL, Redis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few things I've built:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://archstudent.com/" rel="noopener noreferrer"&gt;ArchStudent&lt;/a&gt; — content platform for architecture students&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://quranroshni.com/" rel="noopener noreferrer"&gt;Quran Roshni&lt;/a&gt; — fast, ad-free Quran reader (~30 languages)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://worldhotsprings.com/" rel="noopener noreferrer"&gt;World Hot Springs&lt;/a&gt; — data-pipeline world directory&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://goodping.com/" rel="noopener noreferrer"&gt;GoodPing&lt;/a&gt; — free network tools + uptime monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see more of what I build at &lt;a href="https://jeemmo.com/" rel="noopener noreferrer"&gt;jeemmo.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>wordpress</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Are coders still getting hired now that AI can write code?</title>
      <dc:creator>Azeem Javed</dc:creator>
      <pubDate>Fri, 31 Oct 2025 21:49:12 +0000</pubDate>
      <link>https://dev.to/jeemmo/are-coders-still-getting-hired-now-that-ai-can-write-code-3oa6</link>
      <guid>https://dev.to/jeemmo/are-coders-still-getting-hired-now-that-ai-can-write-code-3oa6</guid>
      <description></description>
    </item>
    <item>
      <title>Wordpress theme development - version update function</title>
      <dc:creator>Azeem Javed</dc:creator>
      <pubDate>Sat, 03 Jun 2023 12:00:31 +0000</pubDate>
      <link>https://dev.to/jeemmo/wordpress-theme-development-version-update-function-2mpi</link>
      <guid>https://dev.to/jeemmo/wordpress-theme-development-version-update-function-2mpi</guid>
      <description>&lt;p&gt;Hello,&lt;/p&gt;

&lt;p&gt;how to add wordpress theme update function in source files? do i need to add a code in function.php ?&lt;/p&gt;

&lt;p&gt;is there any standard code to add? what other files i need to add update functionality code?&lt;/p&gt;

&lt;p&gt;i want to do this.&lt;br&gt;
1- I will take wordpress standard theme files from underscores.me and make it as version 1.0&lt;br&gt;
2- Make some fancy work on it, i will make Landing Pages/sub pages.&lt;br&gt;
3- Make theme documentation for users to read and install.&lt;br&gt;
4- upload on envato market.&lt;/p&gt;

&lt;p&gt;So after 2-3 months if i upgrade my theme as in version 1.1&lt;br&gt;
so how i noptify this to my envato users? where i need to add code in my theme for this function?&lt;/p&gt;

&lt;p&gt;I know i can take api token generate from envato api website and update this in envato plugin in my wordpress dashboard.&lt;/p&gt;

&lt;p&gt;is there anyone doing the same job or have done it?&lt;/p&gt;

&lt;p&gt;Please guide&lt;br&gt;
Best Regards&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
