Author: onetwothreeadmin

  • AI model context windows: when to chunk prompts vs. send everything

    AI model context windows: when to chunk prompts vs. send everything

    AI model context windows: when to chunk prompts vs. send everything
    Photo by Shubham Dhage on Unsplash

    Claude can handle 200,000 tokens. GPT-4 Turbo manages 128,000. Gemini 1.5 Pro claims two million. The context window arms race makes it sound like you should dump everything into a single prompt and let the model sort it out.

    That works sometimes. Other times it burns tokens, produces worse output, and costs more than splitting the work into smaller chunks.

    Here’s when each approach makes sense.

    When the full context window actually helps

    Long context windows shine when the model needs to cross-reference information scattered throughout a document. If you’re asking an AI to summarize a 40-page research report and identify contradictions between sections, sending the whole thing makes sense. The model needs simultaneous access to all sections to spot inconsistencies.

    Same logic applies to code review across multiple files. If you’re debugging an API integration where the authentication logic lives in one file, the request handler in another, and error handling in a third, the model benefits from seeing all three at once.

    Legal document analysis, long-form content editing where voice consistency matters, and multi-chapter manuscript feedback all benefit from full-document context.

    The pattern: tasks that require the model to hold multiple pieces of information in working memory and reason about relationships between them.

    When chunking produces better results

    Counterintuitively, smaller prompts often generate more accurate output for sequential tasks.

    If you’re processing 50 customer support tickets to categorize them by issue type, sending all 50 at once degrades accuracy. Models perform better when they focus on 5–10 tickets at a time. The attention mechanism that makes transformers work distributes focus across the entire context window—more tokens means more diluted attention on each individual item.

    Translation work follows the same rule. Translating a 10,000-word article in one shot produces inconsistent terminology and awkward phrasing in the middle sections. Breaking it into 1,000-word chunks with a glossary of key terms maintains quality.

    Batch operations—tagging blog posts, extracting structured data from invoices, generating social media variants from a content calendar—almost always work better in smaller groups.

    The cost calculation nobody runs

    Context window pricing isn’t linear with most providers. Claude charges the same per token whether you use 1,000 or 100,000, but response quality degrades as you approach the upper limit. You end up re-running prompts, which doubles your cost.

    More importantly: larger contexts generate longer responses by default. The model tries to address everything you’ve included. A 50,000-token input often produces a 3,000-token response when a 5,000-token input would have generated 400 tokens with the same useful information.

    If you’re running 100 operations per day and the difference between chunked and full-context prompts is 2,000 output tokens per run, that’s 200,000 tokens—roughly $2.40 on Claude’s current pricing. Compounds to $876 annually for a task that might not benefit from the extra context.

    How to decide for your specific task

    Run the same operation both ways with a sample of 10–20 items. Score the outputs on accuracy, consistency, and usefulness. Time the runs. Calculate the token cost from your provider’s API logs.

    If chunked prompts score within 5% of full-context quality but cost 40% less and run twice as fast, chunk. If full-context produces meaningfully better results—especially for tasks you run infrequently—use the full window.

    For tasks you’ll repeat weekly or daily, optimization matters. For one-off research deep-dives, throw everything at the model and move on.

    The other variable: how often you iterate. If you’re refining prompts and testing variations, smaller chunks let you experiment faster. Full-context prompts take longer to run and make it harder to isolate what changed between versions.

    Want more practical breakdowns of AI tools for online operators? Subscribe to One Two Three Send—we test this stuff so you don’t have to.

    Heads up — some links in this article are affiliate links. If you sign up through them, we may earn a small commission at no extra cost to you. We only recommend tools we use ourselves.

  • Stop buying productivity tools you’ll abandon in 30 days

    Stop buying productivity tools you’ll abandon in 30 days

    Stop buying productivity tools you'll abandon in 30 days
    Photo by Dmitriy Demidov on Unsplash

    Online operators waste more money on abandoned productivity tools than failed ad campaigns. The pattern is identical every time: you see a demo, imagine the workflow, subscribe to the annual plan for the discount, use it for three weeks, then revert to your old system.

    The problem isn’t the tools. It’s that most operators evaluate software by counting features instead of mapping workflows. A task manager with 47 integrations sounds impressive until you realize you only need three—and your current system already has them.

    The 30-day abandonment window

    Most SaaS products see the steepest drop-off between days 14 and 30. You’re past the onboarding dopamine hit but haven’t built enough muscle memory for the tool to feel faster than your old method. That’s when friction wins.

    The tools that stick past 30 days share one trait: they eliminate a specific pain point you experience multiple times per day. A social scheduling tool saves you from logging into four platforms every morning. A snippet expander removes the need to retype the same email three times a week. A invoice tracker stops you from hunting through Gmail for payment confirmations.

    Tools that promise to “organize your entire business” almost always fail because the pain they solve is abstract. You don’t feel disorganized multiple times per day—you feel it as a low-grade background hum. That’s not enough activation energy to change behavior.

    Run the replacement audit first

    Before you subscribe to anything new, open a text file and list every tool the new one would replace. Not the features it could replace—the tools you will actually stop paying for if you switch.

    If the list is empty, you’re adding to your stack, not replacing. That’s fine for tools under $15/month, but anything above that needs to justify itself by removing something else. Otherwise you’re paying twice for the same capability.

    Next, track how many times per week you currently experience the problem the tool claims to solve. If it’s fewer than five, the tool won’t stick. You need repeated friction to build new habits. A monthly invoicing tool might be perfect for agencies, but if you only invoice quarterly, you’ll forget the workflow between uses.

    Test with the free tier longer than you think

    Most free trials last 14 days. That’s barely enough time to import your data, let alone stress-test a workflow under real conditions. If a tool offers a free tier with limits instead of a time-boxed trial, use it for 60 days minimum.

    The goal isn’t to see if the tool works—that’s what demos are for. You’re testing whether you naturally reach for it when the problem appears. Do you open the new task manager when a client emails a request, or do you still add it to your old system and tell yourself you’ll migrate it later?

    Watch for workflow duplication. If you’re maintaining two systems in parallel “just in case,” the new tool hasn’t actually replaced the old one. That’s a strong signal it won’t stick.

    When annual plans actually save money

    Annual discounts only save money if you use the tool for the full year. Obvious, but operators ignore it constantly. A 40% annual discount on a $25/month tool means you prepay $180 instead of $300. If you abandon it at month four, you’ve spent $180 to use a $100 tool.

    The break-even point for annual plans is usually month eight or nine. If you’re not confident you’ll still be using the tool then, pay monthly. The extra cost is insurance against switching costs.

    One exception: tools with painful migration paths. If moving your email list or content library to a new platform takes more than four hours, an annual plan makes sense earlier—because the switching cost itself will keep you locked in whether you prepaid or not.

    If you’ve caught yourself opening a tool you pay for and forgetting what you use it for, reply and tell me which one. I’m tracking the most commonly abandoned categories for a future piece.

    The tools that survive past 30 days aren’t the ones with the best feature lists. They’re the ones that remove something you hated doing every single day—and make the replacement feel faster than the original from day one.

  • Most online operators track revenue wrong—it compounds

    Most online operators track revenue wrong—it compounds

    Most online operators track revenue wrong—it compounds
    Photo by KOBU Agency on Unsplash

    Revenue tracking sounds straightforward: money comes in, you record it, you know what you made. But most solo operators and small teams get it wrong in ways that compound over time—distorting decision-making, complicating taxes, and hiding which parts of the business actually work.

    The error isn’t usually a miscounted invoice. It’s structural: tracking cash received instead of revenue earned, mixing gross and net figures, or failing to reconcile platform payouts with actual sales. These mistakes don’t stay isolated. They cascade into poor pricing decisions, inaccurate runway projections, and tax filings that require expensive amendments.

    Cash accounting hides what’s actually happening

    Most operators track revenue when money hits their bank account. That’s cash accounting, and it works fine until you start dealing with delayed payouts, refunds issued weeks later, or affiliate networks that pay 60 days in arrears.

    When you record Stripe revenue on the day of payout instead of the day of sale, your August numbers include sales from July and miss the last week of August entirely. Add in a refund from June that processes in August, and your month-over-month comparison becomes meaningless.

    Accrual accounting—recording revenue when the sale happens, regardless of when cash moves—fixes this. You track the sale on August 3, even if Stripe pays you on August 10. Refunds get recorded against the original sale month. Affiliate commissions get logged when earned, not when paid.

    This isn’t about compliance. It’s about knowing whether August was actually better than July, or whether you’re looking at a payout-timing illusion.

    Gross revenue vs. net revenue: the numbers diverge fast

    Stripe takes 2.9% plus 30 cents per transaction. Gumroad takes 10%. Affiliate networks take 20–30%. If you’re recording gross sales as revenue but paying expenses from net proceeds, your P&L is structurally wrong.

    Here’s what happens: you see $10,000 in sales, set aside 25% for taxes ($2,500), then realize you only received $8,500 after platform fees. Now you’re $1,500 short on your tax estimate, and that gap compounds every month.

    The fix is simple but requires discipline: decide whether you’re tracking gross or net, then apply it consistently across every revenue source. Most operators should track gross revenue and record platform fees as a cost of goods sold or merchant fee expense. That way, you can compare effective take-rates across Stripe, Gumroad, and direct PayPal invoices on equal footing.

    If you’re using a spreadsheet, add columns for gross, fees, and net. If you’re using accounting software, create separate accounts for platform fees and map them correctly during import.

    Platform dashboards lie by omission

    Stripe’s dashboard shows gross volume. Beehiiv‘s dashboard shows net revenue after their cut. ConvertKit shows gross subscription value but doesn’t subtract payment processing fees unless you export the full transaction CSV.

    If you’re pulling numbers from multiple dashboards and adding them together, you’re mixing gross and net without realizing it. The total is wrong, and worse, it’s wrong in a way that drifts further from reality as you add more revenue streams.

    The only fix is a single source of truth: a spreadsheet, a proper accounting tool like QuickBooks or Xero, or at minimum a dedicated revenue tracker like Baremetrics or ProfitWell. Import or manually enter every transaction with the same structure: date, gross, fees, net, source, product. Reconcile monthly against bank deposits.

    This sounds tedious, but it takes 20 minutes a month and prevents the six-hour reconciliation nightmare in January when you’re trying to close the year.

    Why this compounds

    Revenue tracking errors don’t just distort historical reports—they corrupt forward-looking decisions. If you think a product made $3,000 last quarter but it actually netted $2,100 after fees and refunds, you might double down on it instead of testing alternatives. If you believe your business grew 15% month-over-month when the real figure is 8%, you might overspend on hiring or tools.

    Tax filings compound the problem further. If your revenue tracking doesn’t match your 1099-K forms from payment processors, you’ll either overpay taxes or trigger an IRS inquiry. Both cost money and time you don’t have.

    The longer you wait to fix this, the harder it gets. Reconciling six months of transactions across three platforms is miserable. Reconciling 18 months is a billing event for your accountant.

    Start this week: pick one revenue source, export the last 90 days of transactions, and compare the total to what you’ve recorded. If the numbers don’t match within $50, your tracking is broken. Fix that one source, then add the next.

    Revenue tracking isn’t exciting. But it’s the foundation for every other decision you make. Get it right once, and it stays right. Get it wrong, and the error grows every month until you’re forced to stop and rebuild from scratch.

    Want more operator-focused breakdowns like this? Subscribe to One Two Three Send—one article daily, no fluff.

    Heads up — some links in this article are affiliate links. If you sign up through them, we may earn a small commission at no extra cost to you. We only recommend tools we use ourselves.

  • Paid social campaign dashboards update on different schedules

    Paid social campaign dashboards update on different schedules

    Paid social campaign dashboards update on different schedules
    Photo by Nick Brunner on Unsplash

    You check your Facebook Ads Manager at 9 a.m. and see $247 spent with 18 conversions. You open Google Ads in the next tab: $198 spent, 22 conversions. LinkedIn shows $310 spent, 11 conversions. You drop the numbers into a spreadsheet to calculate blended cost-per-acquisition, then check again two hours later. Facebook hasn’t moved. Google added three conversions. LinkedIn subtracted one.

    Welcome to the reality of paid social dashboards: every platform updates metrics on its own schedule, and none of them tell you when the last refresh happened.

    Why update intervals matter

    Most operators treat ad dashboards like bank balances—refresh the page, get the current number. But ad platforms process billions of events across millions of campaigns. They batch updates, prioritize certain metrics over others, and apply attribution windows that shift as more data arrives.

    Facebook Ads Manager typically updates spend and impression data every 15 to 30 minutes during active campaign hours. Conversion data can lag by one to four hours, especially if you’re using the Conversions API or tracking offline events. If you’re running campaigns across multiple time zones, the dashboard may show blended data from different collection windows.

    Google Ads refreshes most metrics every three hours for standard campaigns. Search campaigns update faster—often within 60 minutes—because query data arrives in near-real-time. Display and YouTube campaigns batch less frequently. Conversion imports from Google Analytics 4 can take six to twelve hours to appear, and the attribution window keeps adjusting for up to 90 days if you allow data-driven attribution.

    LinkedIn Campaign Manager updates impressions and clicks roughly every two to four hours. Lead gen form fills appear faster, usually within 30 minutes, because LinkedIn processes them as first-party events. Conversion tracking via the Insight Tag can delay by four to eight hours depending on pixel fire volume and whether you’ve enabled enhanced conversions.

    When stale data breaks decisions

    The update lag becomes a problem when you’re making budget decisions across platforms in the same session. If Facebook shows yesterday’s conversion total but Google reflects this morning’s, your cost-per-acquisition comparison is measuring different time windows. You might pause a LinkedIn campaign that looks expensive at 11 a.m., only to see five conversions arrive by 3 p.m. that would have changed the math.

    One operator I spoke with pulls reporting at the same time every day—8 a.m. Eastern—and assumes all platforms are showing data through midnight. He realized Facebook was still processing West Coast conversions from 9 p.m. to midnight, so his morning snapshots undercounted by 12 to 18 percent. He shifted his reporting pull to 10 a.m. and saw the discrepancy drop to under 5 percent.

    The other trap: platforms don’t update all metrics simultaneously. Facebook might refresh spend in real time but hold conversion data for the next batch cycle. If you calculate return on ad spend while spend is current but conversions are three hours old, you’re blending live and lagged numbers.

    How to work around dashboard lag

    Set a consistent daily reporting time at least four hours after your highest-traffic period ends. If most conversions happen between 6 p.m. and 10 p.m., pull reports after 2 p.m. the next day. That gives every platform time to process overnight events and apply attribution.

    Use each platform’s bulk export or API for anything you’re putting into a spreadsheet. Exports often pull from a more complete dataset than the live dashboard. Facebook’s Ads Reporting API lets you specify a date range and attribution window; the response includes a data freshness timestamp so you know exactly when the numbers were last calculated.

    When comparing platforms, add a staleness column. Mark which metrics updated in the last hour, the last four hours, or earlier. If LinkedIn conversion data is eight hours old and Facebook’s is two hours old, flag the comparison and revisit it later rather than making a budget shift on mismatched windows.

    For operators running high-frequency tests—creative swaps, bid adjustments, audience shifts—check the platform’s API documentation for real-time vs. batched endpoints. Google Ads has a real-time reporting service for search campaigns that updates every few minutes, but it costs more in API quota. Facebook offers a similar accelerated delivery insight, though it’s not exposed in the standard dashboard.

    When to trust the dashboard and when to wait

    Spend data is almost always current within 30 minutes across all three platforms. Impressions and clicks update faster than conversions. If you need a quick pulse check on whether a campaign is delivering traffic, the dashboard is reliable. If you’re calculating return on ad spend or cost per lead, wait until the conversion window closes.

    One pattern I’ve seen work: check dashboards for pacing and delivery in the morning, but reserve budget and optimization decisions for end-of-day when conversion data has caught up. If a campaign looks like it’s burning budget without results at 10 a.m., tag it for review but don’t pause it until 5 p.m. unless spend is wildly over target.

    Platforms won’t fix this. Real-time everything is expensive to build and even more expensive to scale. The update intervals we have now are design choices that balance infrastructure cost against operator needs. Understanding the lag is part of the job.

    What’s your reporting routine? Do you pull data at the same time daily, or do you check dashboards ad hoc? Hit reply and let me know—I’m collecting operator workflows for a future piece on attribution reconciliation.

  • WordPress REST API rate limits: what trips automated workflows

    WordPress REST API rate limits: what trips automated workflows

    WordPress REST API rate limits: what trips automated workflows
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    If you’re running any kind of automated workflow that touches WordPress—content sync tools, headless front-ends, analytics dashboards, social schedulers—you’re making REST API requests. And sooner or later, you’ll hit a wall you didn’t know existed.

    Most managed WordPress hosts impose rate limits on REST API calls. The problem: they rarely document the exact threshold, and when you exceed it, the failure mode is rarely obvious. Your workflow just… stops working.

    Where the ceiling sits

    Rate limits vary wildly by host and plan tier. WP Engine enforces approximately 60 requests per minute per IP on mid-tier plans. Kinsta’s limit sits closer to 100 requests per minute but drops during peak traffic windows. SiteGround and Bluehost shared plans can throttle as low as 30 requests per minute under load.

    The limit isn’t always per-account—it’s often per-IP or per-site. If you’re running multiple automations from the same server (a Zapier workflow pulling post data while a headless front-end fetches metadata), both count toward the same ceiling.

    BigScoots and similar VPS-adjacent hosts give you more headroom, but you’re still sharing infrastructure unless you’ve negotiated custom limits or moved to dedicated resources.

    How you trip it without noticing

    The most common culprit: bulk operations during content imports or migrations. If you’re syncing 500 posts from an external CMS via REST API, and each post requires three API calls (create post, upload featured image, assign taxonomy terms), that’s 1,500 requests. At 60 requests per minute, you’re looking at 25 minutes—but only if nothing else touches the API during that window.

    Social scheduling tools that auto-pull post excerpts or featured images can silently hammer the API every time they refresh your queue. Analytics plugins that log every page view via REST API can push you over the edge during traffic spikes.

    Headless WordPress setups are especially vulnerable. If your Next.js or Gatsby build process fetches all posts, taxonomies, and media in parallel during deployment, you can blow through your limit in seconds. Incremental static regeneration helps, but only if you’ve tuned the request concurrency.

    How to test your actual limit

    Most hosts won’t tell you the number until you ask support directly—and even then, the answer is often vague (“we recommend staying under 60 requests per minute”). The fastest way to find your real ceiling: controlled load testing.

    Use a tool like curl with a loop, or a lightweight Node script with axios, to hit a non-destructive endpoint (/wp-json/wp/v2/posts?per_page=1) at increasing rates. Start at 30 requests per minute, then 60, then 100. Watch for HTTP 429 responses or sudden timeouts.

    Log the exact rate where you start seeing failures. That’s your ceiling. Build in a 20% buffer—if you hit 429 at 80 requests per minute, design your workflows to stay under 65.

    The non-obvious fix

    Most developers reach for caching or request batching first. Both help, but the real leverage is in request sequencing and backoff logic.

    If you’re building a custom integration, implement exponential backoff: when you hit a 429, wait two seconds, then four, then eight. Most WordPress hosts reset rate-limit counters every 60 seconds, so a brief pause often resolves the issue without killing your workflow.

    For third-party tools (Zapier, Make, n8n), check if they expose retry settings or rate-limit handling. Zapier’s “Delay After Queue” action lets you throttle outbound requests manually. Make’s HTTP module supports custom retry logic via error handlers.

    If you’re running a headless build process, switch from parallel to sequential fetching for high-volume endpoints, or split your build into smaller incremental chunks. Gatsby’s GATSBY_CONCURRENT_DOWNLOAD environment variable lets you cap parallel requests; start at 10 and tune down if you’re still hitting limits.

    One last thing: if you’re on a managed host and legitimately need higher limits for a production workflow, ask. Most hosts will raise the ceiling for established accounts with predictable traffic patterns—but only if you ask before you break something.

    Have a question about WordPress infrastructure or automation limits? Reply to this email—we cover reader questions every Sunday.

  • Lemon Squeezy’s webhook signature verification: how it works

    Lemon Squeezy’s webhook signature verification: how it works

    Lemon Squeezy's webhook signature verification: how it works
    Photo: Iamovichowdhury via Wikimedia Commons (CC BY-SA 4.0)

    If you’re running a paid newsletter, course platform, or membership site on Lemon Squeezy, you’re probably listening for webhooks to provision access, update subscriber status, or log revenue events. But every webhook Lemon Squeezy sends includes a signature header that most operators ignore—and that’s a problem.

    Webhook signature verification isn’t optional security theater. It’s the only way to confirm that the POST request hitting your server actually came from Lemon Squeezy, not a malicious actor replaying old payloads or inventing fake subscription events.

    Here’s how Lemon Squeezy’s signature system works, how to validate it in your code, and one non-obvious edge case that breaks verification even when you’ve done everything right.

    How Lemon Squeezy generates the signature

    Every webhook Lemon Squeezy sends includes an X-Signature header. This is an HMAC-SHA256 hash of the raw request body, signed with your webhook signing secret.

    You’ll find your signing secret in the Lemon Squeezy dashboard under Settings → Webhooks. It looks like a long alphanumeric string starting with whsec_. Each webhook endpoint you create gets its own secret—if you rotate or delete an endpoint, the secret changes.

    The signature process is straightforward: Lemon Squeezy takes the entire JSON payload, hashes it using HMAC-SHA256 with your secret as the key, then sends the resulting hash in the header. Your server’s job is to recreate that hash and compare it to what was sent.

    Validating the signature in your code

    Here’s a minimal Node.js example using Express and the built-in crypto module:

    const crypto = require('crypto');
    const express = require('express');
    const app = express();
    
    const WEBHOOK_SECRET = process.env.LEMON_SQUEEZY_SECRET;
    
    app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
      const signature = req.headers['x-signature'];
      const payload = req.body;
    
      const hash = crypto
        .createHmac('sha256', WEBHOOK_SECRET)
        .update(payload)
        .digest('hex');
    
      if (hash !== signature) {
        console.error('Invalid signature');
        return res.status(401).send('Unauthorized');
      }
    
      // Signature valid—process the webhook
      const event = JSON.parse(payload);
      console.log('Event type:', event.meta.event_name);
    
      res.status(200).send('OK');
    });
    

    Two things to note: you must use express.raw() to preserve the raw request body. If you parse the JSON first with express.json(), the signature won’t match—whitespace, key order, and encoding all matter. You also need to compare the hash as a hex string, not a buffer.

    If you’re using PHP, the process is similar with hash_hmac(). Python uses hmac.new() from the standard library. The algorithm is the same across languages.

    The edge case: timestamp drift and replay attacks

    Lemon Squeezy doesn’t include a timestamp in the signature itself, which means a valid webhook payload can be replayed indefinitely. If an attacker intercepts a legitimate webhook—say, a subscription cancellation event—they can resend it to your endpoint days or weeks later, and your signature check will pass.

    The mitigation is to track processed webhook IDs. Every Lemon Squeezy webhook includes a unique meta.webhook_id field in the payload. Before processing the event, check whether you’ve already seen that ID. If you have, return a 200 status but skip the business logic.

    Store processed IDs in a database table with a TTL—30 days is reasonable. This prevents replay attacks without requiring you to implement a sliding timestamp window.

    Here’s the addition to the earlier example:

    const event = JSON.parse(payload);
    const webhookId = event.meta.webhook_id;
    
    // Check if already processed (pseudo-code)
    if (await db.webhookExists(webhookId)) {
      console.log('Duplicate webhook, ignoring');
      return res.status(200).send('OK');
    }
    
    await db.saveWebhookId(webhookId);
    // Continue processing...
    

    When verification fails even though it shouldn’t

    If your signature check is failing consistently and you’re confident the secret is correct, check your server’s request body size limit. Some frameworks—Express included—default to a 100kb limit. Lemon Squeezy’s order_created webhooks can exceed that if the order includes multiple line items or custom data fields.

    Increase the limit in your middleware config: express.raw({ type: 'application/json', limit: '1mb' }).

    The other common culprit is proxies or load balancers that rewrite the request body. If you’re behind Cloudflare, Nginx, or AWS ALB, confirm that the raw body is being forwarded unmodified. A single trailing newline or whitespace change will break the hash.

    Lemon Squeezy doesn’t currently offer a signature verification test mode or sample payloads with pre-signed signatures, so the easiest way to debug is to log both the computed hash and the received signature, then compare them character by character.

    If you’re handling payments or provisioning access via webhooks, signature verification isn’t optional. It’s the only way to distinguish legitimate events from forged requests. Set it up once, test it with a live webhook, and add replay protection if your product handles high-value transactions.

    Got a question about webhook security or payment automation? Reply to this email—we cover this stuff every week.

  • Substack’s custom domain setup: DNS propagation vs. SSL timing

    Substack’s custom domain setup: DNS propagation vs. SSL timing

    Substack's custom domain setup: DNS propagation vs. SSL timing
    Photo: F1jmm via Wikimedia Commons (CC BY-SA 3.0)

    When you point a custom domain at Substack, the platform tells you it can take “up to 24 hours” for everything to work. That’s technically true, but misleading—because your domain will resolve to Substack’s servers long before the SSL certificate provision finishes.

    This creates a specific problem: readers who visit your new domain during the gap see either a certificate warning or a “not secure” flag in their browser. If you’re migrating from a self-hosted archive or another platform, that window matters more than Substack’s docs suggest.

    What actually happens during the setup window

    Substack’s custom domain process has two distinct phases that don’t finish at the same time.

    First, DNS propagation. You add a CNAME record pointing your subdomain (usually newsletter.yourdomain.com) to subdomain.substack.com. Most DNS providers push that change globally within 5–15 minutes. Once it propagates, requests to your domain resolve to Substack’s infrastructure.

    Second, SSL certificate provisioning. After Substack detects the CNAME, it requests a Let’s Encrypt certificate for your domain. This involves a challenge-response validation that can take anywhere from 10 minutes to several hours, depending on Let’s Encrypt’s queue and how aggressively Substack’s automation retries.

    The gap between the two creates the problem. Your domain is live and serving pages—but without a valid HTTPS certificate. Browsers warn users. Email clients flag links. If you announce the migration before SSL finishes, a portion of your audience sees errors.

    How long the SSL delay actually lasts

    In practice, most Substack custom domains finish SSL provisioning within 90 minutes of DNS propagation completing. I’ve migrated eight newsletters to custom domains on Substack since early 2025; six took under an hour, one took three hours, and one required a support ticket after stalling for 12 hours.

    The stall case was a domain that had previously pointed to another newsletter platform. Substack’s system appeared to cache an older validation attempt and didn’t retry automatically. Support cleared it manually within 20 minutes of the ticket.

    Substack doesn’t expose provisioning status in the dashboard. The settings page shows “DNS configured” as soon as the CNAME resolves, but there’s no separate indicator for “SSL active.” You have to test the domain in a browser and check for the padlock.

    The non-obvious timing trick

    Set up your custom domain at least 48 hours before you announce the migration.

    Substack allows you to configure a custom domain without switching your publication’s primary URL immediately. Once the CNAME is added and SSL finishes provisioning, the custom domain works as an alias—readers can visit it, links resolve correctly, but your posts and emails still use the yourname.substack.com address.

    This gives you a clean testing window. Verify SSL, check that archive links work, confirm that your website’s nav links resolve correctly. Then switch the primary domain in Substack’s settings when you’re ready to announce.

    The switch itself is instant. Substack updates all post URLs and sets up 301 redirects from the old subdomain to the new domain. Emails sent after the switch use the custom domain in from-addresses and link URLs.

    One edge case: if you’re moving from another platform entirely (say, WordPress or Ghost) and pointing an existing domain at Substack, you lose the testing window. The CNAME change is live immediately, so there’s no way to preview without breaking the old site. In that case, either accept the SSL gap or set up the Substack publication on a temporary subdomain first, migrate content, then switch the CNAME during a low-traffic window.

    What to tell readers during the gap

    If you’re migrating during business hours and can’t wait out the SSL delay, send a heads-up email before you flip the CNAME. Something like: “We’re moving to [new domain] today. If you see a security warning in the next hour, that’s expected—refresh in 15 minutes and it’ll clear.”

    Most readers won’t visit during the gap, but the ones who do will appreciate knowing it’s temporary. Browser warnings make people assume they’ve been phished; a short explanation prevents that panic.

    For newsletters with large back catalogs, check your most-linked posts after SSL provisions. Substack’s redirects handle yourname.substack.com/p/post-slug to newsletter.yourdomain.com/p/post-slug correctly, but if you’ve previously shared links with UTM parameters or other query strings, test a few samples to confirm they resolve.

    One Two Three Send publishes operator-focused breakdowns like this one every day. Subscribe to get them in your inbox, or reply with questions about your own custom domain setup—we’ll cover edge cases in a future Q&A piece.

  • Traffic attribution breaks when you change page URLs mid-campaign

    Traffic attribution breaks when you change page URLs mid-campaign

    Traffic attribution breaks when you change page URLs mid-campaign
    Photo by Veit Hammer on Unsplash

    You launch a paid campaign, queue up social posts, send a newsletter blast—then realize the landing page slug is wrong. Or you decide to consolidate two pages. Or you migrate a high-traffic post to a new URL structure.

    The redirect goes live. Traffic keeps flowing. But your attribution data fractures.

    Campaign managers, Google Analytics, and email platforms all track the original URL. When visitors hit a 301 redirect, most analytics tools log the destination page as the conversion point, but the source parameters often stick to the original link. The result: your dashboard shows conversions on the new URL with no clear path back to the campaign that drove them.

    What breaks first: UTM parameters and referrer strings

    When a visitor follows a link with UTM parameters—?utm_source=twitter&utm_campaign=launch—and hits a redirect, the parameters usually survive the hop. Google Analytics 4 and most tag managers preserve them across 301s and 302s.

    But if you change the URL after the campaign link is already distributed—say, in a newsletter that’s already sent or a Facebook ad that’s been running for three days—those links now point to a page that redirects. The UTM params land on the new URL, but your campaign report in GA4 shows two separate page paths: the old slug and the new one. If you’re filtering by page path or using it as a secondary dimension, your conversion count splits across both.

    Referrer strings are worse. If someone clicks a link from an external site (a backlink, a forum post, a guest article), the referring domain is logged by your analytics tool when the page loads. A redirect in the middle often strips or rewrites the referrer header, especially if the redirect chain includes a tracking proxy or a CDN. You lose visibility into where the traffic actually originated.

    Email and ad platforms track the click, not the final destination

    Email platforms like Postmark, MailerLite, and Beehiiv log the link you included in the email. If that link is yoursite.com/old-page, the click report shows clicks to /old-page—even if the visitor ends up on yoursite.com/new-page after the redirect.

    The same applies to Facebook Ads, Google Ads, and LinkedIn Campaign Manager. The platform records the destination URL you entered when you created the ad. If you change the page slug after the ad goes live, the platform’s click and conversion data still references the original URL. Your GA4 data shows the new URL. Matching them up requires manual cross-referencing or custom reporting dimensions.

    If you’re running A/B tests or comparing campaign performance across channels, this split makes it nearly impossible to attribute conversions accurately without a spreadsheet mapping old URLs to new ones.

    How to handle URL changes mid-campaign

    The cleanest fix: don’t change URLs while campaigns are active. If you must rename a page, wait until the campaign ends, or build the new URL from the start and redirect the old one before you launch.

    If the change is unavoidable, update the link in every place you control. Edit the email draft if it hasn’t sent. Pause the ad, change the destination URL, and restart it. Update your social scheduler if posts are still queued. Most platforms let you edit scheduled posts in tools like Publer or Buffer without losing the post slot.

    For links you can’t change—like a newsletter that already went out or a backlink from a published article—set up a 301 redirect and add a note in your analytics tool. In GA4, create a custom event or annotation marking the date you changed the URL. In your campaign spreadsheet, log both the old and new URLs with a note that they’re the same destination. When you pull reports, filter by campaign parameter (utm_campaign) instead of page path.

    If you’re consolidating multiple pages into one, use a URL parameter to distinguish the source. Redirect /old-page-1 to /new-page?from=old1 and /old-page-2 to /new-page?from=old2. GA4 will log the parameter, and you can build a custom report that groups conversions by the from value.

    When attribution is already broken

    If you’ve already changed URLs and your data is split, you can retroactively fix some of it. Export raw event data from GA4 (Explore → Free-form → add Page Path and Campaign as dimensions). Use a spreadsheet to map old URLs to new ones, then build a calculated field or pivot table that consolidates them.

    For email platforms, export the click report and match it to GA4 data by timestamp and user ID. Most ESPs let you export click-level data with timestamps. If your GA4 property is logging User ID or Client ID, you can join the two datasets and attribute conversions back to the email campaign even if the page path doesn’t match.

    It’s manual work, but it’s faster than rebuilding your entire attribution model.

    One tip: if you’re running affiliate campaigns or tracking sponsor conversions, URL changes mid-campaign often void your conversion tracking. Most affiliate networks (ShareASale, Impact, CJ) require you to register the destination URL when you create the link. Changing it without updating the network’s records means conversions won’t be credited. Always check your affiliate dashboard and update the destination URL there too.

    Want more attribution and tracking breakdowns? Subscribe to One Two Three Send—we cover the operational details that break (and how to fix them) every week.

    Heads up — some links in this article are affiliate links. If you sign up through them, we may earn a small commission at no extra cost to you. We only recommend tools we use ourselves.

  • AI prompt versioning: why saving iterations matters more than the final draft

    AI prompt versioning: why saving iterations matters more than the final draft

    AI prompt versioning: why saving iterations matters more than the final draft
    Photo by Zulfugar Karimov on Unsplash

    You’ve spent twenty minutes refining a ChatGPT prompt that generates product descriptions. It works beautifully. Two weeks later, you tweak it for a new product line—and suddenly the old output style vanishes. You can’t remember what you changed. The prompt is gone.

    Most solo operators treat AI prompts like scratch paper: they edit in place, overwrite, and move on. That works until a prompt stops performing, a client asks for the “old version,” or you need to debug why Tuesday’s output doesn’t match Monday’s.

    Prompt versioning—saving each iteration instead of overwriting—solves this. It’s not about perfectionism. It’s about maintaining a working asset when context, models, and requirements shift constantly.

    Why prompts degrade over time

    AI models don’t change day-to-day, but three things do:

    • Your input context. You add a new product category, a different tone requirement, or an edge case. The prompt that worked for fifty items breaks at sixty.
    • Model updates. OpenAI, Anthropic, and others push model improvements without warning. A prompt optimized for GPT-4 in May might behave differently in August.
    • Your memory. You remember the prompt works. You don’t remember which phrasing made it work, or what you removed three edits ago.

    Without versions, you’re guessing. With them, you can diff two iterations, isolate what changed, and roll back when an edit breaks something subtle.

    How to version prompts without overhead

    You don’t need Git. You need a system light enough that you’ll actually use it.

    Option one: dated text files. Create a folder. Name each prompt file with a date and slug: 2026-08-11-product-description.txt. When you edit, save a new file. Costs zero dollars, works offline, and grep searches instantly.

    Option two: a dedicated tool. Tools like PromptLayer, LangSmith, or Humanloop log every prompt and response automatically. You get diffs, performance tracking, and rollback in a UI. Trade-off: another subscription (typically $20–50/month) and vendor lock-in.

    Option three: a Notion database. One table. Columns for version date, prompt text, model used, and sample output. Filter by project. Duplicate a row to create a new version. Free tier handles this fine for solo operators.

    Pick the one you’ll actually maintain. Versioning only works if it’s faster than not doing it.

    What to track in each version

    Don’t just save the prompt text. Capture:

    • Date and version number. Even a simple v1, v2, v3 helps.
    • What changed and why. One sentence: “Added tone constraint for technical audience” or “Removed example because it biased output.”
    • Model and settings. GPT-4, temperature 0.7, max tokens 500. When output shifts, you need to know if the prompt changed or the settings did.
    • Sample output. Paste one good result. Future-you will forget what “good” looked like.

    This takes sixty seconds per version. It saves hours when something breaks.

    When to create a new version vs. edit in place

    Not every typo fix needs a version. Use this rule:

    Create a new version when you change structure, logic, constraints, or examples. Edit in place when you fix typos, formatting, or obvious errors.

    If you’re not sure, version it. Disk space is cheap. Lost prompts aren’t.

    The rollback test

    Here’s how you know versioning is working: a client asks for last month’s email style. You open your prompt archive, copy v8, and regenerate. Done in two minutes.

    Without versions, you’re rewriting from memory, testing five variations, and hoping one feels right.

    Prompts are infrastructure now. Treat them like code: version, document, and never assume you’ll remember what worked.

    What’s one prompt you wish you’d saved three edits ago? Hit reply—I read every response, and reader examples shape future deep-dives here.

  • WordPress plugin auto-updates: when to enable and when to audit first

    WordPress plugin auto-updates: when to enable and when to audit first

    WordPress plugin auto-updates: when to enable and when to audit first
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    WordPress added automatic plugin updates in 2020, but most solo operators still toggle them on or off by instinct. Some enable everything and hope for the best. Others disable all auto-updates and let security patches pile up for months.

    Both approaches fail eventually. A smarter strategy is to classify your plugins by risk, then set auto-update policies based on what breaks when something goes wrong.

    What auto-updates actually do

    When you enable auto-updates for a WordPress plugin, the core update routine checks twice daily for new versions. If a new release is available, WordPress downloads and activates it without asking. No email confirmation. No manual review.

    Minor updates—like going from version 3.4.1 to 3.4.2—usually contain security patches or bug fixes. Major updates—like 3.x to 4.0—often introduce new features, deprecate old functions, or rewrite significant chunks of code.

    WordPress doesn’t distinguish between the two when auto-updating. If the developer ships a major version, your site will install it overnight. That’s where problems start.

    The risk matrix: which plugins to auto-update

    Start by grouping your active plugins into three tiers based on what happens if they break.

    Tier 1: Critical path plugins. These handle forms, checkout flows, email capture, payment processing, or user authentication. If they fail, you lose subscribers or revenue. Examples: WooCommerce, Gravity Forms, MemberPress, any payment gateway integration.

    Never enable auto-updates for Tier 1 plugins. Test major updates in a staging environment first. Even minor updates can introduce conflicts with your theme or other plugins, and you won’t know until someone reports a broken checkout.

    Tier 2: Analytics and third-party integrations. These plugins connect WordPress to external services—Google Analytics, Facebook Pixel, ConvertKit, Zapier webhooks. They rarely touch core site functionality, but when they break, you lose tracking data or automation triggers.

    Enable auto-updates for security patches only if the plugin developer uses semantic versioning and maintains a public changelog. If the developer ships breaking changes without warning, disable auto-updates and check manually once a month.

    Tier 3: Cosmetic and convenience plugins. Syntax highlighters, table-of-contents generators, related-post widgets, social share buttons. These improve the reader experience but don’t affect conversions or data collection.

    Enable auto-updates. If something breaks, you’ll notice it during your next post preview. The risk is low, and staying current reduces the chance of a security exploit in an unmaintained codebase.

    The audit cadence nobody talks about

    Enabling auto-updates isn’t a one-time decision. Plugin developers change ownership, get acquired, or abandon projects. A plugin that was safe to auto-update in January might ship a disastrous update in August.

    Set a quarterly reminder to review your auto-update settings. Check each plugin’s changelog for the last three months. If you see phrases like “major refactor,” “breaking changes,” or “deprecated legacy support,” disable auto-updates and test manually before upgrading.

    Also watch for plugins that haven’t shipped an update in six months. That’s often a sign the developer has moved on. Disable auto-updates, find a replacement, and migrate before a WordPress core update introduces a fatal incompatibility.

    The staging environment exception

    If you run a staging site that mirrors production, you can auto-update aggressively there and manually promote updates only after confirming nothing broke. This works well for operators publishing daily or running membership sites where downtime is expensive.

    The catch: staging environments need to be true mirrors. Same theme, same plugins, same server PHP version, same database size. A lightweight staging site with dummy content won’t catch conflicts that only appear under load or with real user data.

    Most managed WordPress hosts—BigScoots, Kinsta, WP Engine—offer one-click staging environments that sync database and files from production. If your host doesn’t, the manual sync overhead usually outweighs the auto-update safety benefit.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send for weekly operator-to-operator guides on the tools that actually run online businesses.