<?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: Nazma Akter</title>
    <description>The latest articles on DEV Community by Nazma Akter (@nazma_akter_ac1ab9dbccfe6).</description>
    <link>https://dev.to/nazma_akter_ac1ab9dbccfe6</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%2F4189862%2F4e588c22-fc17-47c1-808d-82a5c222039a.png</url>
      <title>DEV Community: Nazma Akter</title>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nazma_akter_ac1ab9dbccfe6"/>
    <language>en</language>
    <item>
      <title>From checkout to "seat booked": how free and paid orders turn into event registrations</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Tue, 20 Oct 2026 05:01:44 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/from-checkout-to-seat-booked-how-free-and-paid-orders-turn-into-event-registrations-3anf</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/from-checkout-to-seat-booked-how-free-and-paid-orders-turn-into-event-registrations-3anf</guid>
      <description>&lt;p&gt;Selling a live event sounds simple until you build it. A payment has to become a seat, the seat has to show up in the buyer's dashboard, and the meeting link should be useful at the right moment and not before. In &lt;a href="https://veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub&lt;/a&gt; events are just another product type, so they go through the same cart and checkout as courses and memberships. The 30-second reel shows the flow. Here's what happens under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. An event is a product with settings, not a separate system
&lt;/h3&gt;

&lt;p&gt;The host creates an event in a 6-step drawer: details, type, schedule, registration, pricing and notifications. Under the hood it's a scheduling product with event settings (type, recurrence, capacity, replay) and one or more occurrences (start, end, time zone, meeting provider, join URL). If the host picks the built-in live room, the live session is created from the event's name and date, so there's no link to paste.&lt;/p&gt;

&lt;p&gt;Prices are attached after the first save, through the same pricing model every other product uses. That keeps the Product List widget and the cart unaware of what an "event" is.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The widget and the cart don't special-case events
&lt;/h3&gt;

&lt;p&gt;The Product List widget the host drops on a page reads the public products endpoint and renders events next to everything else, with their active prices, search, filters and an Add to Cart button. The cart and checkout work the same way for every product type.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Fulfilment happens on the paid order item
&lt;/h3&gt;

&lt;p&gt;When an order is paid, an access service walks its items and grants access per product type. For a scheduling item, it creates the buyer's booking for that event. If the event needs approval (the waitlist option), the booking stays pending and the host is notified. The approval notice is sent after the payment is recorded, so a notification problem can never turn a successful payment into an error for the buyer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Join is a time window, not just a link
&lt;/h3&gt;

&lt;p&gt;The buyer's dashboard shows the booking in a calendar. The Join button only unlocks when two things are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the booking is &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;approved&lt;/code&gt; or &lt;code&gt;rescheduled&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;it's no more than &lt;strong&gt;10 minutes&lt;/strong&gt; before the start. For a recurring event, that means the next upcoming occurrence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before that, the card shows the date and time in the buyer's own time zone. Showing the link too early mostly produces people sitting alone in an empty room.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we'd tell someone building the same thing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Model events as products. Your cart, coupons, checkout and receipts then come for free.&lt;/li&gt;
&lt;li&gt;Fulfil per order item, after payment, and make side effects (emails, notifications) unable to fail the purchase.&lt;/li&gt;
&lt;li&gt;Treat "join" as a window with a status check, not a URL you hand out at checkout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it with a free trial: &lt;a href="https://veloxonhub.com" rel="noopener noreferrer"&gt;https://veloxonhub.com&lt;/a&gt;. I'm happy to go into the booking or time-zone logic in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>nextjs</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Five AI media types, three pipelines: how we built AI Studio in Go and the browser</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Tue, 20 Oct 2026 04:22:53 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/five-ai-media-types-three-pipelines-how-we-built-ai-studio-in-go-and-the-browser-1bbb</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/five-ai-media-types-three-pipelines-how-we-built-ai-studio-in-go-and-the-browser-1bbb</guid>
      <description>&lt;p&gt;&lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub Social&lt;/a&gt; has an AI Studio that makes five kinds of media for social posts: images, infographics, carousels, slide decks and short vertical videos. From the outside it's one form with five tabs. Underneath, it's three pipelines, because the five outputs fail, cost and finish in different ways.&lt;/p&gt;

&lt;p&gt;Here's how each one works, and the decisions that made them reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline 1: images and infographics (synchronous)
&lt;/h3&gt;

&lt;p&gt;An image or an infographic is a single HTTP request that returns a finished file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model routing comes from a table, not code.&lt;/strong&gt; Every model a user can pick lives in an &lt;code&gt;ai_models&lt;/code&gt; table: its OpenRouter id, a display name and which use it is the default for (&lt;code&gt;image&lt;/code&gt;, &lt;code&gt;infographic&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;slides&lt;/code&gt;…). The model picker in the UI is just that table. A &lt;code&gt;google/…&lt;/code&gt; model runs on our Gemini key; anything else goes through OpenRouter. Changing the default image model is a row update, not a deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infographics get a stricter prompt than images.&lt;/strong&gt; Image models invent numbers when given space for a chart. The infographic prompt asks for short, legible text and says, in so many words, &lt;em&gt;do not invent statistics that are not given below&lt;/em&gt;. The form says the same thing to the user: "Give the exact facts and numbers to include; the AI won't make any up."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage is checked before the model is called.&lt;/strong&gt; Generated files go into the workspace's media library, which has a quota. We check for room (with some headroom) &lt;em&gt;before&lt;/em&gt; calling the model, so nobody pays for an image we then can't save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credits are held, then settled or refunded.&lt;/strong&gt; The flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hold the credits for the generation.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;generation&lt;/code&gt; row (prompt, options, model, which key).&lt;/li&gt;
&lt;li&gt;Call the model, store the file, add it to the library.&lt;/li&gt;
&lt;li&gt;Settle the hold on success. On any failure, refund it and mark the row failed with a message a person can read.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A failed generation never costs the user anything, and the history shows &lt;em&gt;why&lt;/em&gt; it failed instead of a spinner that never ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors are split by whose problem they are.&lt;/strong&gt; If our own provider account runs out of quota, that's our problem: the user sees "AI is temporarily unavailable", and we get a log line telling us to check billing. If the user's &lt;em&gt;own&lt;/em&gt; key runs out, they get the provider's message, because they're the one who can fix it. That brings us to keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bring your own key, everywhere
&lt;/h3&gt;

&lt;p&gt;Every workspace can pay with our AI credits or connect its own key. Images run on a Gemini or OpenRouter key, and videos on a Gemini key. The generation row records which key it used, which matters most for the next pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline 2: video (asynchronous)
&lt;/h3&gt;

&lt;p&gt;A video takes minutes to render, so the API can't wait for it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Generate&lt;/code&gt; starts the render with the video model and stores the provider's &lt;strong&gt;operation id&lt;/strong&gt; on the generation row, then returns at once. The UI shows "rendering".&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;worker&lt;/strong&gt; polls the operations that are due, a few at a time. When one finishes, it downloads the file, stores it, adds it to the library and settles the credits.&lt;/li&gt;
&lt;li&gt;A render that's still running after &lt;strong&gt;30 minutes&lt;/strong&gt; is marked failed and its credits are refunded automatically.&lt;/li&gt;
&lt;li&gt;If the user removes the API key a video was started with, the poll fails cleanly with "the API key this video was made with was removed" rather than retrying forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule that kept this simple: &lt;strong&gt;the generation row is the whole state machine.&lt;/strong&gt; The request handler and the worker never talk to each other; they both read and write that row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline 3: carousels and decks (AI writes, the browser draws)
&lt;/h3&gt;

&lt;p&gt;A carousel or a deck is mostly text, and image models are at their weakest with text: words get misspelled, slides in one set don't match, and fixing one word means generating the whole image again.&lt;/p&gt;

&lt;p&gt;So carousels and decks are split in two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The server asks a text model for JSON.&lt;/strong&gt; A carousel is &lt;code&gt;{title, caption, slides: [{heading, body}]}&lt;/code&gt;; a deck is &lt;code&gt;{title, subtitle, slides: [{heading, bullets, notes}]}&lt;/code&gt;. The system prompt sets the shape and the limits: headings of at most 8 words, a hook on the first carousel slide and a call to action on the last, speaker notes for decks, and no invented facts. The parser is deliberately forgiving: it takes the outermost &lt;code&gt;{…}&lt;/code&gt; even if the model wrapped it in prose, trims every field to what the templates can show, drops empty slides and cuts the list to the slide count the user asked for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser draws the slides on a canvas.&lt;/strong&gt; Carousel slides are 1080×1350 (Instagram's 4:5) and deck slides are 1920×1080. A small text-fitting routine wraps each block and shrinks the font in steps until it fits its box, so a long heading never overflows. Because the browser draws the text with system fonts, every script those fonts cover works, and a typo fix is just an edit followed by a redraw. There are five colour themes to switch between, plus an optional footer handle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exports happen client-side too.&lt;/strong&gt; Carousel slides are exported as PNGs. Decks are packed into a PDF by about 40 lines of TypeScript that write the PDF objects by hand: one full-page JPEG per page, with no PDF library. The browser then uploads the files to the library, linked to the generation.&lt;/p&gt;

&lt;p&gt;The result: the AI does the writing it's good at, and the layout is deterministic and editable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we'd tell you if you're building something similar
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep a generation table from day one.&lt;/strong&gt; History, refunds, async polling and failure messages all hang off it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hold credits, don't charge them.&lt;/strong&gt; Settle on success and refund on every failure path, including the boring ones like "the file couldn't be saved".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't let image models draw text you care about.&lt;/strong&gt; Ask a text model for structured content and render it yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put model choice in data.&lt;/strong&gt; New models ship every month, and a table row is easier to change than a release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;AI Studio is part of &lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub Social&lt;/a&gt;. It's included on the Elite plan and is an add-on on Basic and Pro, and every plan starts with a 7-day free trial.&lt;/p&gt;

&lt;p&gt;I'm happy to go deeper on the video polling or the canvas text fitting in the comments.&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Keeping an AI lead researcher honest: "word for word" facts and emails that are never guessed</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 12:47:44 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/keeping-an-ai-lead-researcher-honest-word-for-word-facts-and-emails-that-are-never-guessed-2mm6</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/keeping-an-ai-lead-researcher-honest-word-for-word-facts-and-emails-that-are-never-guessed-2mm6</guid>
      <description>&lt;p&gt;Lead discovery is an easy place for AI to make things up. Ask a model for "the founder of Cedar Creative and their email", and it will happily invent a plausible name and &lt;code&gt;alex@cedarcreative.com&lt;/code&gt;. A cold email to an invented address bounces and hurts your sender reputation. A wrong name is worse.&lt;/p&gt;

&lt;p&gt;When we built lead discovery for &lt;a href="https://reach.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub Reach&lt;/a&gt;, we set one rule for everything shown on a lead: &lt;strong&gt;every fact has to come from a page we actually read, and the user can see that page.&lt;/strong&gt; The 30-second reel shows the flow. Here's how the rule works in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Search returns evidence, not just names
&lt;/h3&gt;

&lt;p&gt;A run takes a plain-language description ("independent social media agencies managing content for 5–20 clients") and searches public websites and indexed LinkedIn, Facebook and X pages through Exa. Each run returns up to 10 results, deduplicated by product and normalized URL, so running the same search again doesn't fill your list with copies.&lt;/p&gt;

&lt;p&gt;Every lead keeps the text that made it match, shown as "Why this lead appeared". The fit score next to it is a keyword match, and the UI labels it as a heuristic, not a purchase probability. We'd rather undersell the score than have someone treat it as a prediction.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Emails are extracted, never guessed
&lt;/h3&gt;

&lt;p&gt;Deep research reads each website lead's contact, about and team pages. An email is added only if it appears in that text. Addresses on the lead's own domain come first, and no-reply and placeholder addresses are dropped. The lead also stores the page the email was found on, so "where did this address come from?" always has a clickable answer.&lt;/p&gt;

&lt;p&gt;We pull phone numbers the same way: from &lt;code&gt;tel:&lt;/code&gt; links, or from numbers that start with &lt;code&gt;+&lt;/code&gt; or follow a phone label. WhatsApp comes only from &lt;code&gt;wa.me&lt;/code&gt; or &lt;code&gt;api.whatsapp.com&lt;/code&gt; links. We don't use heuristics that "look like a phone number" anywhere else on the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI facts must appear word for word
&lt;/h3&gt;

&lt;p&gt;To find named people and company facts, the model reads the same page text and returns owners, founders and leaders with their titles, plus team size and founding year. Then we check each one against the text:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;each fact is kept only if the page text contains it word for word&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the model returns "Alex Rivera, Founder &amp;amp; Creative Director" and that string isn't on the page, it's dropped. No error and no fallback, just an empty field. An empty field is honest, and a made-up one isn't.&lt;/p&gt;

&lt;p&gt;This check also makes failures harmless. If the AI call fails or the workspace is out of credits, the people and company fields stay empty, and the rest of the lead is unaffected.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Research never sends anything
&lt;/h3&gt;

&lt;p&gt;A run can also write a first message for every new lead. Email goes to leads with an address, and LinkedIn and Facebook leads get a manual DM task with an "Open profile" link. But research never sends anything: every draft waits in review. Private profiles are never accessed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we'd tell someone building the same thing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Store the &lt;strong&gt;source URL next to every extracted value&lt;/strong&gt;. It's the cheapest trust feature you can ship.&lt;/li&gt;
&lt;li&gt;Check model output against the text you gave it. Matching it word for word is crude, but it catches a whole class of made-up facts.&lt;/li&gt;
&lt;li&gt;Name your heuristics in the UI. "Keyword match heuristic, not a purchase probability" saves an argument later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it with a 7-day free trial: &lt;a href="https://reach.veloxonhub.com" rel="noopener noreferrer"&gt;https://reach.veloxonhub.com&lt;/a&gt;. I'm happy to go into the matching or the Exa setup in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why we treat chat as a product, not a channel</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:30:41 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/why-we-treat-chat-as-a-product-not-a-channel-1dd7</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/why-we-treat-chat-as-a-product-not-a-channel-1dd7</guid>
      <description>&lt;p&gt;Most creators who sell a paid community run it on rented tools. The payment goes through Stripe or Gumroad, the conversation happens in WhatsApp or Discord, and someone has to remember to remove people when they stop paying.&lt;/p&gt;

&lt;p&gt;While building Group Chat for &lt;a href="https://veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub&lt;/a&gt;, we made one decision that most of the design follows from: &lt;strong&gt;a group chat is a product&lt;/strong&gt;, just like a course or a membership.&lt;/p&gt;

&lt;h3&gt;
  
  
  What "chat as a product" means in practice
&lt;/h3&gt;

&lt;p&gt;A group chat has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prices.&lt;/strong&gt; Chat prices are the same price objects the rest of the platform uses, so a room can be sold as a subscription, a one-time purchase or any other pricing model the editor supports. We didn't write separate billing code for chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Members and entitlement.&lt;/strong&gt; Who can read and post is decided by entitlement, not by an invite link. When a subscription ends, access ends with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A public page.&lt;/strong&gt; Each room gets SEO metadata and a public sales page, so people can find a paid room through search and buy it, just like a course landing page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan limits.&lt;/strong&gt; The number of rooms is a plan limit (5, 20 or unlimited), checked the same way as other products.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The realtime part
&lt;/h3&gt;

&lt;p&gt;Messages are delivered over a socket. History is loaded over REST and the socket pushes new messages. On top of that we built what people expect from a modern chat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reactions, pins, edit and delete&lt;/li&gt;
&lt;li&gt;voice notes recorded in the browser, plus file attachments&lt;/li&gt;
&lt;li&gt;mute, read marks, unread badges and online status&lt;/li&gt;
&lt;li&gt;message reporting that goes into an admin moderation queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Private 1-to-1 chat and group chat run on &lt;strong&gt;one shared chat engine&lt;/strong&gt;. Creators can sell paid access to themselves as a separate product, and we only maintain one messaging stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two lessons we learned
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Read state lives in two places.&lt;/strong&gt; The server stores read marks, and the client keeps local marks so the UI responds instantly. If you update only one of them, the unread badge shows the wrong count. Every path that marks a conversation as read now updates both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Every message write has to go through the live connection.&lt;/strong&gt; We once added a message action that wrote only through REST. It saved correctly but didn't appear for anyone until they reloaded. Now the socket hook owns the live state, and every write path must go through it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it isn't
&lt;/h3&gt;

&lt;p&gt;To be clear about the limits: this is messaging between a creator and their audience, not a Slack replacement. It has no voice or video channels and no nested channels with threads. We left those out on purpose. The goal is a room people pay to join that sits next to the course they bought, not a workplace tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;If you sell courses, coaching or a community and you're tired of maintaining a Discord bot just to check who has paid, have a look: &lt;a href="https://veloxonhub.com" rel="noopener noreferrer"&gt;https://veloxonhub.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm happy to answer questions in the comments about the entitlement model or the socket setup.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>startup</category>
    </item>
    <item>
      <title>One post, 14 networks: how our composer handles per-network text, limits and timing</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:01:02 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/one-post-14-networks-how-our-composer-handles-per-network-text-limits-and-timing-lch</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/one-post-14-networks-how-our-composer-handles-per-network-text-limits-and-timing-lch</guid>
      <description>&lt;p&gt;Write once, post everywhere" is the promise of every social media scheduler. While building the composer for &lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub Social&lt;/a&gt;, we found that most of the work is in the exceptions. Bluesky caps a post at 300 characters, and LinkedIn allows 3,000. Some networks need an image, and others can't take the one you attached. Posts are scheduled across time zones. And none of these networks accept formatted text.&lt;/p&gt;

&lt;p&gt;Here's how we handle each of these. There's also a 2-minute video of the composer in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. One base text, optional overrides per network
&lt;/h3&gt;

&lt;p&gt;A post has one &lt;code&gt;content&lt;/code&gt; and a &lt;code&gt;platform_options&lt;/code&gt; map keyed by network. The text a network will actually get is resolved in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking "Customize text" copies the current base text into that network's override. From then on, the base text and the override are edited separately until you click "Use main text". Network-specific fields live in the same map: a YouTube title and visibility, a Facebook link, a title, status and tags for Dev.to and WordPress, and tags for Tumblr. Before the post is saved, options for networks that are no longer selected are dropped, so a deselected network doesn't leave stale settings behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validate each network, and give each problem a fix
&lt;/h3&gt;

&lt;p&gt;Validation runs separately for each network on the text and media that network would actually receive (&lt;code&gt;textFor(p)&lt;/code&gt;, &lt;code&gt;mediaFor(p)&lt;/code&gt;), not on the base post. Each problem is shown with the network's name, such as "Bluesky allows 300 characters (335 now)". The Schedule button stays disabled until every problem is fixed.&lt;/p&gt;

&lt;p&gt;Media problems get a one-click fix. When a network can't take the attached files, the warning shows a &lt;strong&gt;"Remove media for LinkedIn"&lt;/strong&gt; button. It sets a &lt;code&gt;no_media&lt;/code&gt; flag in that network's options, and the media resolver then returns nothing for that network. The other networks still get the image.&lt;/p&gt;

&lt;p&gt;We count characters with &lt;code&gt;[...text].length&lt;/code&gt;, which counts code points, not &lt;code&gt;text.length&lt;/code&gt;. An emoji or a "bold" letter is one code point but two UTF-16 units, so &lt;code&gt;text.length&lt;/code&gt; would make the counter disagree with the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Formatting" is Unicode
&lt;/h3&gt;

&lt;p&gt;Social APIs accept plain text, so the editor's bold, italic and monospace buttons map letters to the Mathematical Alphanumeric Symbols block (sans-serif bold starts at &lt;code&gt;U+1D5D4&lt;/code&gt;). Underline and strikethrough add combining marks (&lt;code&gt;U+0332&lt;/code&gt;, &lt;code&gt;U+0336&lt;/code&gt;). The result survives copy-paste and every network's API exactly as typed. Bullet styles (&lt;code&gt;•&lt;/code&gt;, &lt;code&gt;✅&lt;/code&gt;, &lt;code&gt;👉&lt;/code&gt;…), numbered lists, quotes and dividers are line prefixes, so they're plain text too.&lt;/p&gt;

&lt;p&gt;One trade-off to be honest about: screen readers may read styled letters one character at a time. We keep formatting for emphasis and don't apply it to whole paragraphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Time is a wall-clock time plus a zone
&lt;/h3&gt;

&lt;p&gt;The date picker holds a wall-clock time like &lt;code&gt;2026-10-24T09:00&lt;/code&gt; in your profile's timezone, or in the zone you pick for that post, which is saved with it. It's converted to an instant only when the post is saved. If you change the timezone after picking a time, we keep the wall-clock time, so "9:00" now means 9:00 in the new zone. That's what people expect when they switch from New York to London.&lt;/p&gt;

&lt;p&gt;Each account can override the post's time with &lt;strong&gt;Post's time&lt;/strong&gt;, &lt;strong&gt;Publish now&lt;/strong&gt; or &lt;strong&gt;Own time&lt;/strong&gt;. These overrides are sent as separate &lt;code&gt;account_schedules&lt;/code&gt;, so one post can go out to LinkedIn at 9:00 in Europe and to Instagram at 18:00 in New York.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Retries are part of the record
&lt;/h3&gt;

&lt;p&gt;Every publish attempt is logged with its outcome: &lt;code&gt;published&lt;/code&gt;, &lt;code&gt;transient_error&lt;/code&gt;, &lt;code&gt;permanent_error&lt;/code&gt; or &lt;code&gt;auth_error&lt;/code&gt;. Temporary errors (network failures, HTTP 429 and 5xx) are retried automatically with backoff. Validation rejections fail right away, because sending the same request again won't fix them. Authorization errors trigger a token refresh, or ask you to reconnect the account. The post page shows the full history for each network, so "it posted on the second try" is something you can see, not just guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  See it
&lt;/h3&gt;

&lt;p&gt;The reel shows the full flow: accounts → AI draft → formatting → templates → media → previews → per-network versions → timezone and per-account scheduling → approvals → retries and history → duplicate. 7-day free trial, no card: &lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;https://social.veloxonhub.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions about the per-network override model or the Unicode formatter are welcome in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Installed, but nothing shows": building an install check for a third-party widget</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:26:14 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/installed-but-nothing-shows-building-an-install-check-for-a-third-party-widget-1j1m</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/installed-but-nothing-shows-building-an-install-check-for-a-third-party-widget-1j1m</guid>
      <description>&lt;p&gt;Any product that ships a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag for customers to paste into their site gets the same support ticket: &lt;em&gt;"I installed it, but nothing shows."&lt;/em&gt; Most of the time the snippet is fine, and something else is in the way.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://proofloom.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub ProofLoom&lt;/a&gt;, our social proof widget, we built the setup flow so it ends when the widget is working, not when the code is copied. The 15-second reel shows the whole flow. Here's how it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Prove you own the domain
&lt;/h3&gt;

&lt;p&gt;A widget that shows "Liam from Austin just purchased…" on any domain that copies the snippet would be a problem, so notifications only run on verified domains. We offer two ways to verify, because different people control different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A DNS TXT record&lt;/strong&gt;, for people who can reach their DNS. The setup screen explains that many providers (Cloudflare, GoDaddy, Namecheap…) add the root domain to the Name field automatically, so you only enter the subdomain part. This is the most common reason verification fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A verification file&lt;/strong&gt; served at &lt;code&gt;/.well-known/proofloom-verification.txt&lt;/code&gt;, for people who can upload files but can't change DNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: One script, with platform-specific steps
&lt;/h3&gt;

&lt;p&gt;The snippet is the same everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://proofloom.veloxonhub.com/widget/v1/widget.js"&lt;/span&gt; &lt;span class="na"&gt;data-site=&lt;/span&gt;&lt;span class="s"&gt;"site_…"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What changes from platform to platform is where to paste it, so the install panel asks "Where is your site built?" and shows the steps for WordPress, Shopify, Webflow, Wix, Squarespace, Next.js / React, Google Tag Manager or any site. Some no-code builders' code boxes don't accept &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags. For those, there's a plain JavaScript version that loads the same widget.&lt;/p&gt;

&lt;p&gt;The script is &lt;code&gt;async&lt;/code&gt; and never blocks rendering. If our servers are down, the customer's site works as usual. You lose the notifications, not the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: The install check
&lt;/h3&gt;

&lt;p&gt;This is the part that cut down the support tickets. &lt;strong&gt;Test installation&lt;/strong&gt; opens the customer's page from our server and runs a list of checks, each of which passes, fails, warns or is skipped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain verified.&lt;/strong&gt; Is the host verified for this website?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snippet on the page.&lt;/strong&gt; Can we open the page, and is the script tag there? If the page doesn't load, we show the actual error, such as a DNS lookup failure or a timeout, and point out that the page has to be public. Firewalls that block bots are a common cause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site ID matches.&lt;/strong&gt; Is it the snippet for &lt;em&gt;this&lt;/em&gt; website, or one copied from another workspace or a staging site?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not blocked by Content Security Policy.&lt;/strong&gt; A strict &lt;code&gt;script-src&lt;/code&gt; or &lt;code&gt;connect-src&lt;/code&gt; blocks the widget silently, with no visible error. We read the CSP header and tell the customer which directive needs our origin.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When we can't open the page, the later checks are marked "Checked once we can open your page" instead of failing. A failure would suggest that those checks found a problem.&lt;/p&gt;

&lt;p&gt;The check also runs once on its own if a website has never seen the widget. "Installed, but zero visits" is the most confusing state, so we start explaining it before anyone asks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two smaller things that helped
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consent mode.&lt;/strong&gt; Add &lt;code&gt;data-consent="required"&lt;/code&gt; to the script tag, and the widget waits until your banner calls &lt;code&gt;ProofLoom.consent(true)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A preview flag.&lt;/strong&gt; Add &lt;code&gt;?proofloom_preview=1&lt;/code&gt; to any page to see the widget with labelled sample data, without counting a visit. This lets customers check the design before they have any real activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;

&lt;p&gt;14-day free trial, no card needed: &lt;a href="https://proofloom.veloxonhub.com" rel="noopener noreferrer"&gt;https://proofloom.veloxonhub.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've built an install flow for an embeddable widget, I'd like to hear which check caught the most problems for you.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>saas</category>
      <category>webdev</category>
    </item>
    <item>
      <title>One post, 14 networks: how our composer handles per-network text, limits and timing</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:01:10 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/one-post-14-networks-how-our-composer-handles-per-network-text-limits-and-timing-92e</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/one-post-14-networks-how-our-composer-handles-per-network-text-limits-and-timing-92e</guid>
      <description>&lt;p&gt;Write once, post everywhere" is the promise of every social media scheduler. While building the composer for &lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;VeloxonHub Social&lt;/a&gt;, we found that most of the work is in the exceptions. Bluesky caps a post at 300 characters, and LinkedIn allows 3,000. Some networks need an image, and others can't take the one you attached. Posts are scheduled across time zones. And none of these networks accept formatted text.&lt;/p&gt;

&lt;p&gt;Here's how we handle each of these. There's also a 2-minute video of the composer in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. One base text, optional overrides per network
&lt;/h3&gt;

&lt;p&gt;A post has one &lt;code&gt;content&lt;/code&gt; and a &lt;code&gt;platform_options&lt;/code&gt; map keyed by network. The text a network will actually get is resolved in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking "Customize text" copies the current base text into that network's override. From then on, the base text and the override are edited separately until you click "Use main text". Network-specific fields live in the same map: a YouTube title and visibility, a Facebook link, a title, status and tags for Dev.to and WordPress, and tags for Tumblr. Before the post is saved, options for networks that are no longer selected are dropped, so a deselected network doesn't leave stale settings behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Validate each network, and give each problem a fix
&lt;/h3&gt;

&lt;p&gt;Validation runs separately for each network on the text and media that network would actually receive (&lt;code&gt;textFor(p)&lt;/code&gt;, &lt;code&gt;mediaFor(p)&lt;/code&gt;), not on the base post. Each problem is shown with the network's name, such as "Bluesky allows 300 characters (335 now)". The Schedule button stays disabled until every problem is fixed.&lt;/p&gt;

&lt;p&gt;Media problems get a one-click fix. When a network can't take the attached files, the warning shows a &lt;strong&gt;"Remove media for LinkedIn"&lt;/strong&gt; button. It sets a &lt;code&gt;no_media&lt;/code&gt; flag in that network's options, and the media resolver then returns nothing for that network. The other networks still get the image.&lt;/p&gt;

&lt;p&gt;We count characters with &lt;code&gt;[...text].length&lt;/code&gt;, which counts code points, not &lt;code&gt;text.length&lt;/code&gt;. An emoji or a "bold" letter is one code point but two UTF-16 units, so &lt;code&gt;text.length&lt;/code&gt; would make the counter disagree with the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Formatting" is Unicode
&lt;/h3&gt;

&lt;p&gt;Social APIs accept plain text, so the editor's bold, italic and monospace buttons map letters to the Mathematical Alphanumeric Symbols block (sans-serif bold starts at &lt;code&gt;U+1D5D4&lt;/code&gt;). Underline and strikethrough add combining marks (&lt;code&gt;U+0332&lt;/code&gt;, &lt;code&gt;U+0336&lt;/code&gt;). The result survives copy-paste and every network's API exactly as typed. Bullet styles (&lt;code&gt;•&lt;/code&gt;, &lt;code&gt;✅&lt;/code&gt;, &lt;code&gt;👉&lt;/code&gt;…), numbered lists, quotes and dividers are line prefixes, so they're plain text too.&lt;/p&gt;

&lt;p&gt;One trade-off to be honest about: screen readers may read styled letters one character at a time. We keep formatting for emphasis and don't apply it to whole paragraphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Time is a wall-clock time plus a zone
&lt;/h3&gt;

&lt;p&gt;The date picker holds a wall-clock time like &lt;code&gt;2026-10-24T09:00&lt;/code&gt; in your profile's timezone, or in the zone you pick for that post, which is saved with it. It's converted to an instant only when the post is saved. If you change the timezone after picking a time, we keep the wall-clock time, so "9:00" now means 9:00 in the new zone. That's what people expect when they switch from New York to London.&lt;/p&gt;

&lt;p&gt;Each account can override the post's time with &lt;strong&gt;Post's time&lt;/strong&gt;, &lt;strong&gt;Publish now&lt;/strong&gt; or &lt;strong&gt;Own time&lt;/strong&gt;. These overrides are sent as separate &lt;code&gt;account_schedules&lt;/code&gt;, so one post can go out to LinkedIn at 9:00 in Europe and to Instagram at 18:00 in New York.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Retries are part of the record
&lt;/h3&gt;

&lt;p&gt;Every publish attempt is logged with its outcome: &lt;code&gt;published&lt;/code&gt;, &lt;code&gt;transient_error&lt;/code&gt;, &lt;code&gt;permanent_error&lt;/code&gt; or &lt;code&gt;auth_error&lt;/code&gt;. Temporary errors (network failures, HTTP 429 and 5xx) are retried automatically with backoff. Validation rejections fail right away, because sending the same request again won't fix them. Authorization errors trigger a token refresh, or ask you to reconnect the account. The post page shows the full history for each network, so "it posted on the second try" is something you can see, not just guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  See it
&lt;/h3&gt;

&lt;p&gt;The reel shows the full flow: accounts → AI draft → formatting → templates → media → previews → per-network versions → timezone and per-account scheduling → approvals → retries and history → duplicate. 7-day free trial, no card: &lt;a href="https://social.veloxonhub.com" rel="noopener noreferrer"&gt;https://social.veloxonhub.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions about the per-network override model or the Unicode formatter are welcome in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Customer Trust with VeloxonHub ProofLoom: A Product Tour</title>
      <dc:creator>Nazma Akter</dc:creator>
      <pubDate>Sat, 17 Oct 2026 05:40:20 +0000</pubDate>
      <link>https://dev.to/nazma_akter_ac1ab9dbccfe6/building-customer-trust-with-veloxonhub-proofloom-a-product-tour-33k8</link>
      <guid>https://dev.to/nazma_akter_ac1ab9dbccfe6/building-customer-trust-with-veloxonhub-proofloom-a-product-tour-33k8</guid>
      <description>&lt;p&gt;Visitors are landing on your website. Before they buy, they’re asking one question: “Can I trust this brand?”&lt;br&gt;
Meet VeloxonHub ProofLoom. 🚀&lt;br&gt;
Turn real customer activity and approved testimonials into social proof that helps visitors buy with confidence.&lt;br&gt;
In this product tour, see how you can:&lt;br&gt;
✅ Display social proof notifications powered by real orders and activity&lt;br&gt;
✅ Collect and showcase customer testimonials with permission&lt;br&gt;
✅ Customize campaigns to match your brand&lt;br&gt;
✅ Manage social channels and your content calendar in one place&lt;br&gt;
✅ Track campaign performance with built-in analytics&lt;br&gt;&lt;br&gt;
Built around one simple belief: social proof only works when it’s true.&lt;/p&gt;

&lt;h1&gt;
  
  
  VeloxonHub #ProofLoom #SocialProof #SaaS #Ecommerce #CustomerTrust #CustomerTestimonials #CustomerReviews #ConversionOptimization #DigitalMarketing #EcommerceMarketing #SocialMediaManagement #ContentMarketing #MarketingTools #OnlineBusiness #BusinessGrowth #ProductDemo #ProductTour #BuildInPublic #SaaSProduct
&lt;/h1&gt;

</description>
      <category>saas</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>marketing</category>
    </item>
  </channel>
</rss>
