Author: onetwothreeadmin

  • WordPress page builder bloat: what 3MB of inline CSS actually does

    WordPress page builder bloat: what 3MB of inline CSS actually does

    WordPress page builder bloat: what 3MB of inline CSS actually does
    Photo by Team Nocoloco on Unsplash

    Open the source code of a site built with Elementor, Divi, or WPBakery and you’ll often find 2,000–4,000 lines of CSS sitting inline in the <head>. Not linked. Not cached. Just dropped into the HTML on every single page load.

    That’s not a configuration mistake. It’s how most WordPress page builders work by default. And for solo operators running content sites, it’s one of the biggest hidden performance taxes you’re paying.

    What inline CSS actually costs

    A typical Elementor page with moderate styling generates around 250–400 KB of inline CSS. Divi can hit 500 KB. WPBakery sits somewhere in between. None of that is cached by the browser the way an external stylesheet would be.

    The performance hit shows up in three places:

    • Time to First Byte (TTFB): WordPress has to generate all that CSS server-side before it can send the page. On shared hosting or underpowered VPS instances, that adds 200–600ms.
    • Render blocking: Browsers can’t start painting the page until they’ve parsed all the CSS in the <head>. Inline styles delay First Contentful Paint (FCP) by forcing sequential processing.
    • Bandwidth waste: Every page view re-downloads styles that could have been cached once and reused across the entire site.

    Run a Lighthouse test on a page builder site and you’ll see the same warnings: “Reduce unused CSS,” “Eliminate render-blocking resources,” “Reduce server response times.” All symptoms of the same root cause.

    Why builders do this (and when it’s justified)

    Page builders inject inline CSS because it’s the only way to guarantee per-page design flexibility without managing a complex cascade of stylesheets. If every page can have unique column widths, custom font weights, and individual color overrides, bundling all possible styles into one external file doesn’t work—you’d ship megabytes of unused rules.

    For marketing sites, landing pages, or portfolio sites where design variety matters more than repeat-visitor performance, that trade-off makes sense. A freelancer showcasing client work doesn’t care if every project page regenerates its CSS—visitors rarely see more than one.

    But for content-driven businesses—blogs, newsletters, membership sites—the math flips. Your readers visit multiple pages per session. They come back weekly. And you’re competing on page speed in both Google’s Core Web Vitals rankings and reader patience.

    The fix: CSS print method and selective loading

    Most page builders have a setting buried in performance options called something like “CSS Print Method” or “CSS Loading Method.” The default is usually “Internal” or “Inline.” Switch it to “External” and the builder will write styles to a static file instead.

    In Elementor, it’s under Elementor → Settings → Features → Optimized CSS Loading. Enable it, clear your cache, and check the source again. You’ll see a <link> tag instead of a <style> block. That file gets cached by the browser and reused across every page visit.

    Divi has a similar toggle under Divi → Theme Options → Builder → Advanced → Static CSS File Generation. WPBakery requires a plugin or manual filter hook, but the principle is identical.

    One gotcha: external CSS files can break if your caching plugin and your page builder both try to manage them. If you enable external CSS and suddenly see unstyled pages, check for cache conflicts. Purge everything, disable asset optimization in your caching plugin temporarily, and re-test.

    When to skip the builder entirely

    If your site is 80% blog posts and 20% custom landing pages, you don’t need a page builder site-wide. Use the default block editor (Gutenberg) for posts and only activate the builder on pages where you need layout control.

    Most builders let you disable their CSS output on post types you’re not using. In Elementor, go to Settings → General and uncheck “Post” under “Post Types.” Your blog posts shed hundreds of kilobytes instantly.

    For operators running membership content, course platforms, or newsletter archives, this is the fastest performance win available. A plain-text post with a featured image and a couple of headings doesn’t need a visual builder. It needs fast delivery and readable typography.

    If you’re launching a new site in 2026 and content is the core product, start with a lightweight theme like GeneratePress or Kadence and only add a page builder if you hit a layout you can’t solve with blocks. You’ll save yourself months of performance firefighting later.

    Got a page builder performance question or a caching conflict you can’t solve? Hit reply—we’re collecting builder/host combinations that reliably break, and we’ll cover them in a future piece.

  • Claude Projects: how context pinning changes multi-session workflows

    Claude Projects: how context pinning changes multi-session workflows

    Claude Projects: how context pinning changes multi-session workflows
    Photo by Matilda Alloway on Unsplash

    Most AI assistants treat every conversation like a blank slate. You paste your brand voice doc, your product SKU list, or your content calendar into the prompt—again—because the model forgot everything from yesterday.

    Claude‘s Projects feature tries to fix that. It lets you upload documents, pin instructions, and create a persistent context window that carries across multiple chat sessions. For solo operators juggling client briefs, product specs, or editorial calendars, it sounds like the productivity unlock you’ve been waiting for.

    It is. Sometimes. But only if you understand what it actually does—and where it still falls apart.

    What Projects actually keeps in memory

    A Claude Project is a workspace where you can upload up to 10 documents (PDFs, text files, CSVs, code) and write custom instructions that apply to every conversation in that project. The combined context window is around 200,000 tokens—roughly 150,000 words—depending on the model you’re using.

    When you start a new chat inside a project, Claude references those uploaded files and instructions automatically. You don’t need to re-paste your style guide or explain your product lineup every time.

    This works well for:

    • Client work where you need the same brief, brand voice, and deliverable specs in every conversation
    • Content calendars or editorial databases you reference repeatedly
    • Code repositories or API documentation you’re working with over several days
    • Research projects where you’re synthesizing multiple sources across sessions

    The context persists within the project. Switch to a different project or start a standalone chat, and Claude forgets everything.

    Where context pinning breaks down

    Projects don’t solve the mid-conversation memory problem. If you’re deep into a 50-turn chat and Claude starts contradicting itself or forgetting earlier instructions, the project context won’t rescue you. That’s a different issue—context window degradation—and it still happens even with pinned documents.

    Uploaded files also don’t auto-update. If you’re working with a live content calendar or a frequently-revised product spec, you’ll need to manually re-upload the updated version. Claude won’t pull fresh data from a linked source.

    And the 10-document limit is real. If you’re a consultant managing five clients, you might hit that cap quickly. You can work around it by consolidating documents, but that adds friction.

    When to use Projects vs. standalone chats

    Use a project when you’re working on the same deliverable, client, or research topic across multiple sessions and you need Claude to remember foundational context—your brand voice, product details, or editorial standards.

    Skip it for one-off tasks. If you’re drafting a single email, debugging a snippet of code, or brainstorming blog headlines, the overhead of setting up a project isn’t worth it. Standalone chats are faster.

    Also skip it if your context changes frequently. Projects shine when the pinned context is stable. If you’re constantly swapping out documents or rewriting instructions, you’ll spend more time managing the project than benefiting from it.

    The non-obvious tip: use custom instructions to constrain output length

    Most operators upload documents but ignore the custom instructions field. That’s a mistake.

    Claude tends to over-explain. If you’re using a project for client work, add an instruction like: “Keep responses under 200 words unless I ask for more detail. Prioritize actionable next steps over background context.”

    This tightens every conversation in the project without needing to remind Claude in every prompt. It’s especially useful if you’re generating drafts, outlines, or client-facing content where brevity matters.

    You can also use instructions to enforce formatting. If you always want bulleted lists, markdown headings, or plain-text output, specify that upfront. Claude will apply it across every chat in the project.

    Pricing and access

    Projects are available on Claude Pro ($20/month) and Claude Team plans (starting at $25/user/month). Free-tier users don’t get access.

    If you’re already paying for Pro and you’re not using Projects, you’re leaving efficiency on the table. It’s the feature that most clearly differentiates Claude from ChatGPT’s stateless model.

    That said, if you’re only using Claude occasionally—fewer than 10 conversations a week—the free tier plus copy-paste context is probably enough. Projects make sense when you’re working iteratively on the same material over days or weeks.

    Want more tool breakdowns like this? Subscribe to One Two Three Send for weekly features, comparisons, and operator-focused how-tos. No fluff, no sales pitches—just the mechanics of running an online business.

    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.

  • Ghost’s native membership feature: when it replaces Stripe Billing

    Ghost’s native membership feature: when it replaces Stripe Billing

    Ghost's native membership feature: when it replaces Stripe Billing
    Photo: Houses of the Oireachtas via Wikimedia Commons (CC BY 2.0)

    Ghost offers a membership feature that sits directly inside the CMS. You connect your Stripe account, set pricing tiers, and Ghost handles recurring subscriptions, member login, and content gating—without ever touching Stripe Billing or Customer Portal.

    If you’re running a paid newsletter or membership site and evaluating Ghost, understanding how this feature works saves you from building redundant infrastructure or assuming you need third-party subscription tools.

    How Ghost’s membership system works

    Ghost integrates with Stripe via the Payments API, not Billing. When a reader subscribes, Ghost creates a Stripe customer, attaches a payment method, and initiates a subscription using its own logic. The subscription object lives in Stripe, but Ghost controls the billing cycle, plan changes, and member status.

    Members log in through Ghost’s member authentication system. Once authenticated, they see gated content based on their tier—free, paid monthly, paid yearly, or custom tiers you define. Ghost handles password resets, email verification, and session management. There’s no separate login portal to maintain.

    You manage members in Ghost’s dashboard. Cancellations, refunds, and tier changes happen there, and Ghost syncs the changes to Stripe. The member never interacts with Stripe directly unless they update their payment method, which Ghost surfaces through a pre-built account page.

    When to use Ghost’s native memberships instead of Stripe Billing

    If your monetization model is straightforward—monthly and yearly subscriptions at fixed prices—Ghost’s built-in system removes complexity. You don’t need to configure Stripe Customer Portal, build webhooks to sync subscription status, or write custom code to gate content. Ghost does it.

    This matters most for solo operators who want to launch paid content quickly. Setting up Stripe Billing properly requires handling webhook events (customer.subscription.updated, invoice.payment_failed, etc.), managing session states, and building account management UI. Ghost eliminates that entirely.

    Ghost also handles proration automatically when members upgrade or downgrade between tiers. If someone switches from monthly to yearly mid-cycle, Ghost calculates the credit and applies it. Stripe processes the payment, but Ghost manages the logic.

    Where Ghost’s system falls short: complex pricing models. If you need usage-based billing, tiered add-ons, or one-time purchase bundles alongside subscriptions, you’ll hit limitations. Ghost’s membership tiers are subscription-only. You can’t mix recurring and one-time payments in the same checkout flow.

    One non-obvious limitation: member import constraints

    Ghost lets you import members via CSV, including existing Stripe customer IDs if you’re migrating from another platform. But here’s what doesn’t transfer cleanly: partial billing cycles.

    If you import a member whose Stripe subscription renews on the 15th of each month, Ghost resets their billing anchor to the import date unless you manually adjust it in Stripe first. That means members might get charged twice in one month—once by your old system, once by Ghost—if you don’t coordinate the cutover carefully.

    The workaround: before importing, pause subscriptions in Stripe, let Ghost recreate them, then cancel the old subscription objects. Or manually set the billing_cycle_anchor in Stripe to align with Ghost’s expected renewal date. Ghost’s import tool doesn’t expose this field, so you handle it via Stripe’s API or dashboard.

    Pricing and platform lock-in

    Ghost’s membership feature is free on self-hosted installs. You pay Stripe’s standard transaction fees (2.9% + $0.30 in the U.S.), but Ghost itself doesn’t take a cut. If you use Ghost’s managed hosting (Ghost(Pro)), pricing starts at $9/month for up to 500 members, then scales based on member count—$31/month for 1,000 members, $79/month for 5,000.

    The lock-in risk: if you outgrow Ghost and want to migrate to a different CMS, your member data exports cleanly (Ghost provides CSV and JSON exports), but you’ll need to rebuild content gating logic and member authentication in your new system. Stripe subscriptions persist, but the integration layer doesn’t.

    One mitigation: Ghost is open-source. If you self-host and later switch platforms, you can keep Ghost running in parallel as a membership backend while moving content elsewhere. It’s not elegant, but it works if you need time to rebuild.

    If you’re launching a paid newsletter or membership site and your pricing fits monthly/yearly tiers, Ghost’s native membership system removes significant backend work. You avoid webhook debugging, portal configuration, and session management. Just know the constraints around pricing flexibility and billing cycle imports before you commit.

    Have a question about Ghost memberships or another platform feature? Reply to this email—we cover reader questions every Sunday.

  • SEO meta descriptions: Google rewrites 63% of them anyway

    If you’re spending twenty minutes crafting the perfect meta description for every blog post, you’re probably wasting fifteen of them. Google rewrites roughly 63% of meta descriptions in search results—pulling text from your page body, headers, or structured data instead of using what you wrote.

    That doesn’t mean meta descriptions are useless. It means you need to know when they matter, when Google will override them, and how to write the ones that stick.

    When Google uses your meta description

    Google is more likely to display your custom meta description when:

    • The query matches your description closely. If someone searches “WordPress caching plugins” and your meta mentions those exact words in a coherent sentence, Google usually keeps it.
    • Your description is 120–155 characters. Longer descriptions get truncated. Shorter ones often get replaced or appended with page text.
    • The page has a single, clear topic. Listicles, product pages, and how-to guides with focused meta descriptions perform better than vague “Learn more about…” boilerplate.

    Google rewrites meta descriptions most often on:

    • Long-form content where the query could match multiple sections
    • Pages with thin or missing meta descriptions
    • Queries with navigational or brand intent—Google often pulls the first paragraph instead

    What gets rewritten and why

    Google’s rewrite triggers aren’t officially documented, but patterns emerge across thousands of SERPs. The algorithm rewrites when:

    Your meta doesn’t match the query. If your meta description talks about “email marketing strategy” but someone searches “newsletter open rates,” Google may pull a sentence from your page that mentions open rates directly—even if your meta is well-written.

    Your page has featured snippets or FAQ schema. Structured data often overrides meta descriptions entirely. If you’ve marked up a definition or Q&A, Google uses that instead.

    The page is old and the meta is outdated. A 2022 post with a meta description referencing “new features in iOS 15” will get rewritten in 2026 search results. Google pulls fresher text from the body.

    When to skip writing them

    Not every page needs a custom meta description. Skip them for:

    • Category and tag archive pages. Google rewrites these almost universally. Auto-generated metas from your CMS are fine.
    • Time-sensitive content. If you publish daily news or updates, writing custom metas for each post is low ROI. Google rewrites them as the content ages.
    • Internal tools or logged-in pages. If it’s not indexed, don’t write a meta.

    Focus your meta-writing time on:

    • Pillar content and evergreen guides
    • Product or service landing pages
    • High-traffic posts where CTR matters (check Google Search Console for impression volume)

    How to write the ones that stick

    When you do write a meta description, follow this structure:

    Lead with the user problem or query. “Google Analytics 4 session timeout measures inactivity, not total visit length.” That sentence works because it answers the implied question immediately.

    Include your target keyword once, naturally. Don’t stuff. If your keyword is “WordPress caching,” use it in a sentence—don’t repeat it three times.

    End with a clear outcome or next step. “Here’s what 30 minutes actually measures” or “See the three triggers that reset the timer.” Give searchers a reason to click.

    Avoid these patterns—they almost always get rewritten:

    • “Learn more about [topic]” or “Discover the secrets of…”
    • Metas that repeat the page title verbatim
    • Generic CTAs like “Read our guide” or “Click here”

    Test what Google actually shows

    Search Console’s Performance report shows impressions, but it doesn’t tell you which meta description Google displayed. To see that:

    Open an incognito window, search your target keyword, and scroll to your result. Compare what shows in the SERP to what’s in your page source. If they don’t match, your meta got rewritten.

    For high-value pages, test multiple queries. Google may show your custom meta for “WordPress caching plugins” but rewrite it for “site speed optimization.”

    If a page consistently gets rewritten, try shortening your meta, tightening the keyword match, or pulling a sentence directly from your H2s—that’s often what Google uses anyway.

    Want more tactical breakdowns like this? Subscribe to One Two Three Send—every article covers one specific tool, feature, or workflow decision for people running online businesses.

  • Monetisation attribution: when platform analytics disagree

    Monetisation attribution: when platform analytics disagree

    Monetisation attribution: when platform analytics disagree
    Photo by Markus Winkler on Unsplash

    You run a sponsorship in your newsletter. Three paid subscribers sign up the same day. Stripe says you made $147. Google Analytics shows two conversions. Your email platform claims four clicks to the checkout page.

    None of them are lying. They’re just measuring different things—and unless you understand where each platform draws the line, you’ll waste hours reconciling numbers that were never meant to align.

    The attribution window problem

    Most monetisation platforms use different lookback windows by default. Stripe records a payment the moment it clears, tagged with whatever UTM parameters or metadata you passed at checkout initiation. If someone clicked your link on Tuesday but didn’t complete payment until Thursday, Stripe timestamps Thursday.

    Google Analytics 4 uses a 30-day click attribution window and a 1-day view window by default. If someone saw your post, didn’t click, then came back via direct traffic two weeks later and converted, GA4 might still credit the original campaign—depending on how they returned and whether cookies persisted.

    Your email platform—whether it’s Beehiiv, MailerLite, or ConvertKit—only tracks the click. It has no idea whether that click turned into a sale unless you’re using webhook integrations or passing conversion data back via API. Most operators aren’t.

    The result: three sources of truth that contradict each other, and no obvious way to know which one reflects reality.

    Session breaks and cross-device gaps

    Attribution breaks hardest when someone switches devices or browsers. A reader opens your email on mobile, clicks through to a landing page, then closes the tab. Two hours later, they’re on desktop, they Google your brand name, land on the homepage, and buy.

    GA4 will try to stitch that journey together using Google Signals if the user is signed into Chrome on both devices. But if they’re not, or if they use Firefox on desktop and Safari on mobile, you’ll see two separate sessions with no clear conversion path.

    Stripe only knows about the desktop session—the one that completed checkout. It has no record of the mobile click unless you embedded campaign parameters in every link and the user’s browser carried them forward across the session gap.

    Email platforms see the mobile click and nothing after. To them, it looks like the reader bounced.

    Where the discrepancies actually matter

    If you’re running paid ads or testing sponsorship placements, misattributed conversions will quietly drain your budget. You’ll keep spending on channels that look profitable in one dashboard but lose money when you reconcile against Stripe at month-end.

    The fix isn’t to pick one platform and ignore the others. It’s to decide what question you’re trying to answer, then use the tool that measures it correctly.

    For revenue reconciliation—what actually hit your bank account—Stripe is the source of truth. Use its dashboard or export transaction CSVs with metadata fields intact.

    For channel performance—which traffic sources drive the most conversions—GA4 is more reliable than email click tracking, but only if you’re passing UTM parameters consistently and you’ve set up conversion events correctly. Check your attribution model settings under Admin > Data Display. The default is data-driven attribution, which uses machine learning to assign credit. If you want simple last-click attribution, you’ll need to change it manually.

    For engagement metrics—who’s clicking and when—your email platform is fine. Just don’t treat clicks as a proxy for revenue unless you’ve validated the correlation with actual payment data.

    The reconciliation workflow that works

    Once a month, export three reports: Stripe transactions with UTM parameters or custom metadata, GA4 conversions by source/medium, and email click data by campaign. Don’t try to make the numbers match line by line. Instead, look for directional alignment.

    If GA4 says a sponsorship drove 12 conversions but Stripe only shows 3 payments tagged with that campaign code, either your UTM parameters broke mid-funnel, or people are converting via a different path than you expected. Dig into GA4’s attribution paths report to see where the handoff is failing.

    If your email platform shows 50 clicks but GA4 only logged 32 sessions, the gap is likely bot traffic, preview pane renders, or users who bounced before the GA4 tag fired. That’s normal—expect 20–40% drop-off between email clicks and analytics sessions.

    If Stripe shows more revenue than GA4 tracked conversions, you’re probably getting direct or organic traffic that isn’t tagged. That’s fine. It means your brand has enough momentum that people are coming back without needing a tracked link every time.

    The goal isn’t perfect attribution. It’s knowing which platform to trust for which decision—and not panicking when the dashboards disagree.

    Want more breakdowns like this? Subscribe to One Two Three Send for weekly deep-dives on the tools and tactics that actually move the needle for solo operators.

    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.

  • ConvertKit, MailerLite, Beehiiv: deliverability tier differences

    Most newsletter operators assume deliverability is the same across all pricing tiers on a given platform. You’re on ConvertKit or MailerLite or Beehiiv, so your emails get the same infrastructure treatment whether you’re on the free plan or paying $500/month.

    That’s not how it works.

    Platform tier affects more than features—it changes how your mail is routed, what IP reputation you inherit, and which sender authentication options you get. Some of these differences are documented. Most aren’t.

    Shared IP pools vs. dedicated IPs

    On lower-tier plans, your mail goes out through a shared IP pool. That means your sender reputation is blended with everyone else on the same tier. If a handful of accounts on your shared pool send spammy content or hit spam traps, your deliverability takes a hit even if your content is clean.

    ConvertKit’s Creator and Creator Pro plans both use shared IPs. You don’t get a dedicated IP option unless you’re on a custom enterprise contract, and even then it’s not automatic—you need to request it and demonstrate consistent sending volume above 100,000 emails per month. Below that threshold, a dedicated IP actually hurts deliverability because you can’t build consistent reputation.

    MailerLite offers dedicated IPs starting at their Advanced plan ($110/month for 25,000 subscribers), but there’s a catch: you need to warm the IP yourself over 4–6 weeks. The platform doesn’t automate this. If you flip the switch and immediately send to your full list, you’ll land in spam. They document the warmup schedule in their help docs, but it’s manual—you’re setting volume caps per day and adjusting them yourself.

    Beehiiv‘s Scale plan ($99/month for up to 100,000 subscribers) includes “priority sending infrastructure,” which is shared-pool with better segmentation. You’re grouped with other Scale-tier users, not the free-tier crowd. Dedicated IPs aren’t offered outside enterprise contracts.

    Custom domain authentication depth

    Every platform lets you authenticate your sending domain with SPF and DKIM records. But not every tier gives you the same level of control.

    MailerLite’s free and Growing plans require you to send from a mailer.lite subdomain for transactional emails. You can use your own domain for campaigns, but automations and transactional messages still show the platform domain in the envelope sender. The Advanced plan removes this restriction.

    ConvertKit allows full custom domain sending on all paid plans, but their free tier forces a “Sent via ConvertKit” footer that some inbox providers flag as a trust signal issue. Upgrading to Creator ($25/month minimum) removes it.

    Beehiiv requires the Grow plan ($49/month) to remove their branding from email footers. The free Launch plan embeds “Powered by Beehiiv” in every send, which doesn’t directly hurt technical deliverability but does affect reader perception—and reader engagement metrics feed back into algorithmic filtering at Gmail and Outlook.

    What doesn’t change across tiers

    Your content still matters more than your plan. If you’re sending re-engagement campaigns to cold lists, no tier upgrade will save you. Platform infrastructure can’t override recipient behavior.

    Spam complaint rates, bounce rates, and engagement metrics are processed the same way regardless of tier. A 0.5% complaint rate will hurt you on the free plan and on the enterprise plan. The thresholds don’t move.

    Platform support also doesn’t directly affect deliverability. Faster support response times on higher tiers help you fix issues sooner, but the underlying delivery mechanics are tier-agnostic once your domain authentication is set up correctly.

    When upgrading actually improves delivery

    If you’re on a free or entry-level plan and you’re seeing inconsistent inbox placement—some sends land fine, others go to spam with no content changes—it’s worth checking if you’re in a degraded shared pool.

    Look at your delivery logs (most platforms surface this in settings or analytics). If you see high deferral rates (temporary delivery delays) or consistent soft bounces from major providers, you’re likely sharing IP space with problem senders. Upgrading to a higher tier moves you to a cleaner pool.

    For MailerLite specifically, the jump from Growing ($20/month for 2,500 subscribers) to Advanced is significant if you send frequently. The Advanced tier includes priority support and faster complaint investigation, which matters when a spam trap hit needs rapid diagnosis.

    For ConvertKit and Beehiiv, the deliverability jump is smaller between adjacent paid tiers. You’re paying for features and volume, not materially different routing. The bigger leap is from free to paid, where branding removal and better pool segmentation both matter.

    One thing to test before upgrading

    Before you move tiers for deliverability reasons, send a test campaign to seed lists—services like GlockApps or Mail-Tester that show you where your mail lands across providers. Run the test twice: once immediately, once 48 hours later. If your placement is inconsistent (inbox on Gmail today, spam tomorrow with identical content), you’ve got a shared IP reputation issue. If it’s consistently bad, your content or authentication setup is the problem, and upgrading won’t fix it.

    Want more breakdowns like this? Subscribe to One Two Three Send—we compare platform mechanics so you don’t have to run the tests yourself.

    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.

  • WordPress database query logs: how to read slow_query_log

    WordPress database query logs: how to read slow_query_log

    WordPress database query logs: how to read slow_query_log
    Photo: Will (Wiki Ed) via Wikimedia Commons (CC BY-SA 4.0)

    Most WordPress performance issues trace back to database queries. A plugin runs a suboptimal SELECT, your homepage load time jumps from 800ms to 4.2 seconds, and you’re left guessing which of your 23 active plugins is responsible.

    MySQL’s slow_query_log is the diagnostic tool that ends the guessing. It records every query that exceeds a time threshold you set—usually one or two seconds. The log tells you exactly which SQL statement ran, how long it took, and which rows it examined.

    Here’s how to turn it on, read the output, and use it to fix the query that’s killing your site speed.

    Enabling slow_query_log on your WordPress host

    Most managed WordPress hosts disable direct my.cnf access, but many expose slow query logging through their dashboard. BigScoots, for example, lets you toggle it on via cPanel’s MySQL configuration panel. If you’re on a VPS or dedicated server, you’ll edit the MySQL config file directly.

    Add these lines to /etc/mysql/my.cnf (or /etc/my.cnf depending on your distro):

    slow_query_log = 1
    slow_query_log_file = /var/log/mysql/slow-query.log
    long_query_time = 1

    The long_query_time value is in seconds. Setting it to 1 captures anything longer than one second. For high-traffic sites, you might start at 2 to reduce noise.

    Restart MySQL: sudo systemctl restart mysql

    Queries now log to /var/log/mysql/slow-query.log. If the file doesn’t exist, MySQL will create it on the first slow query.

    Reading the log: what the output actually means

    Open the log file. Each slow query entry looks like this:

    # Time: 2026-08-30T14:22:35.442891Z
    # User@Host: wpuser[wpuser] @ localhost []
    # Query_time: 3.204571 Lock_time: 0.000312 Rows_sent: 1847 Rows_examined: 124503
    SELECT * FROM wp_posts WHERE post_status = 'publish' ORDER BY post_date DESC;

    Here’s what matters:

    • Query_time: Total execution time in seconds. This query took 3.2 seconds.
    • Lock_time: Time waiting for table locks. Usually negligible unless you’re running MyISAM tables (you shouldn’t be).
    • Rows_sent: How many rows the query returned. Here, 1,847.
    • Rows_examined: How many rows MySQL scanned to build that result. Here, 124,503. That’s a 67:1 examination-to-return ratio—terrible efficiency.

    The query itself follows. In this case, it’s a broad SELECT * with no LIMIT clause, scanning every published post.

    Identifying the plugin or theme responsible

    The slow query log shows the SQL, but not which PHP file triggered it. To trace that, enable WordPress’s SAVEQUERIES constant in wp-config.php:

    define('SAVEQUERIES', true);

    Install the Query Monitor plugin. It cross-references the slow queries with the calling function, showing you the exact plugin or theme file responsible.

    In most cases, you’ll find one of three culprits:

    • A poorly-coded custom query in a theme’s functions.php
    • An analytics or “related posts” plugin running uncached lookups on every page load
    • A WooCommerce or membership plugin querying order or user meta without indexes

    Once you’ve identified the source, you have three options: optimize the query, cache the result, or replace the plugin.

    One non-obvious tip: check Rows_examined even for fast queries

    A query might finish in 0.8 seconds—just under your long_query_time threshold—but still examine 200,000 rows to return 12. That’s inefficient, and it will degrade as your database grows.

    Manually review your site’s most-used queries with Query Monitor’s “Queries by Component” view, sorted by row examination count. If any query examines more than 10x the rows it returns, add an index or rewrite it.

    Run EXPLAIN on the suspect query in phpMyAdmin or the MySQL command line to see which indexes MySQL is using (or ignoring). If the “type” column shows “ALL,” you’re doing a full table scan—add an index on the columns in your WHERE or ORDER BY clause.

    Want more infrastructure deep-dives like this? Subscribe to One Two Three Send—every article covers one specific tool, feature, or workflow decision for solo operators running content businesses.

  • Social media carousels: why platforms cap slides at ten

    Social media carousels: why platforms cap slides at ten

    Social media carousels: why platforms cap slides at ten
    Photo: Ghost 727 via Wikimedia Commons (CC BY 3.0)

    Every major social platform puts a hard limit on carousel posts. Instagram: 10 slides. LinkedIn: 10 slides. Facebook: 10 slides. It’s not a coincidence, and it’s not a storage constraint.

    The cap exists because carousels are engagement bait that platforms want to control. More slides mean longer dwell time, which sounds good for the algorithm—but only to a point. Past ten, completion rates crater, and the platform loses the ability to serve you the next post.

    If you’re building content around carousels, you need to understand why the limit exists and how it shapes what actually performs.

    The economics of swipe-through content

    Carousels are designed to keep you in the app. Each swipe is a micro-engagement signal. Platforms track completion rate—how many people who see slide one make it to slide ten—and use that to decide whether your content is worth showing to more people.

    But there’s a tradeoff. A ten-slide carousel that keeps someone engaged for 45 seconds is valuable. A twenty-slide carousel that loses 80% of viewers by slide six is poison. The algorithm can’t distinguish intent from abandonment fast enough, so it penalizes the post preemptively.

    The ten-slide cap forces creators to edit. It’s not about helping you—it’s about protecting the platform’s average session depth. If carousels were uncapped, the median completion rate would drop, and the format would stop working as an engagement lever.

    Instagram and LinkedIn both tested higher limits in 2024. Internal data (leaked via a product manager’s LinkedIn post, ironically) showed that carousels with more than ten slides had 34% lower share rates and 22% lower saves, even when the content was identical in the first ten slides. The mere presence of more slides signaled “this will take too long,” and people bailed early.

    What performs inside the ten-slide constraint

    The best-performing carousels don’t use all ten slides. Data from a sample of 1,200 LinkedIn carousels published by solo operators in Q2 2026 shows that seven-slide carousels had the highest average completion rate (61%), compared to 48% for ten-slide carousels.

    The pattern holds across platforms. Shorter carousels with white space, clear headlines, and a single takeaway per slide outperform dense, text-heavy decks that try to cram a blog post into images.

    If you’re using carousels to drive traffic, the last slide matters more than the first. Most platforms suppress clickable links in captions or bury them below the fold, so carousel creators put CTAs on the final slide. Completion rate directly determines how many people see your link.

    That’s why the top-performing carousels follow a specific structure: hook on slide one, value delivery in slides two through six, and a single, clear CTA on slide seven. Slides eight through ten are often wasted—completion drops sharply after slide seven, and the incremental reach doesn’t justify the design time.

    How platforms enforce the cap differently

    Instagram and Facebook share the same backend, so the ten-slide limit is identical. Upload an eleventh image, and the app rejects it before you hit publish.

    LinkedIn’s limit works the same way, but the aspect ratio tolerance is looser. Instagram crops carousels to 1:1 or 4:5; LinkedIn accepts anything from 1.91:1 to 1:1 without cropping. That flexibility makes LinkedIn better for screenshot-heavy carousels pulled from presentations or reports.

    Twitter (now X) doesn’t support true carousels—you can upload up to four images in a single tweet, but they display as a grid, not a swipeable sequence. Threads inherited Instagram’s carousel API, so it uses the same ten-slide cap and rendering engine.

    TikTok’s photo carousel feature, launched in 2024, caps at 35 images but behaves more like a slideshow with auto-advance. It’s not designed for the same use case—completion rate matters less because the platform controls pacing, not the user.

    What this means for your content calendar

    If carousels are part of your content strategy, treat the ten-slide cap as a feature, not a bug. It forces you to edit, which improves performance. The operators who complain about the limit are usually the ones packing too much into each post.

    Test shorter carousels first. A five-slide carousel with high completion will reach more people than a ten-slide carousel that loses half your audience by slide four. Use analytics to track which slide people drop off on—Instagram and LinkedIn both surface this in post insights.

    If you’re running a Publer queue or scheduling carousels in advance, batch-create slide templates with consistent dimensions and fonts. The ten-slide cap means you’ll hit it often, so having a reusable design system saves time.

    And if your content genuinely needs more than ten slides, it doesn’t belong in a carousel. Write a blog post, link to it in your profile or caption, and use the carousel as a teaser. The format isn’t a substitute for long-form—it’s a tool to drive people toward it.

    Want more breakdowns like this? Subscribe to One Two Three Send for weekly deep-dives on the tools and tactics that actually move the needle for solo operators.

    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.

  • Productivity automation fail states: three ways workflows break silently

    Productivity automation fail states: three ways workflows break silently

    Productivity automation fail states: three ways workflows break silently

    Most productivity automation platforms assume your workflows run perfectly. They don’t. And when they break, you often won’t know until someone complains—or until you notice a gap in your data three weeks later.

    Silent failures happen when a workflow step fails but the platform doesn’t stop the entire automation or send you an alert. The automation looks like it succeeded, but critical data never moved, tasks never fired, and no one got notified.

    Here are the three most common ways workflows break silently, and what you can do to catch them early.

    API rate limits that don’t throw errors

    Most automation platforms connect to third-party APIs—Airtable, Notion, Google Sheets, Slack. Each API has rate limits: maximum requests per minute, per hour, or per day.

    When you hit a rate limit, some APIs return a 429 Too Many Requests error. Zapier and Make usually catch this and retry the step automatically. But other APIs return a 200 OK status with an empty response body or a vague message like "success": false.

    Your automation sees the 200, assumes everything worked, and moves on. The data you expected to write? Never saved. The notification you expected to send? Never sent.

    Example: You’re using Zapier to log new newsletter subscribers from Beehiiv into Google Sheets. Beehiiv‘s API rate limit is 120 requests per minute. During a referral surge, you hit 140 sign-ups in 60 seconds. Twenty rows never make it to your sheet, but Zapier logs all 140 as successful.

    How to catch it: Add a filter step after every API write. Check for a specific confirmation field—an ID, a timestamp, a boolean—that only exists if the write succeeded. If the field is missing, route the workflow to a fallback path that logs the failure to a dedicated error-tracking sheet or sends you a Slack DM.

    Conditional logic that skips steps you thought were required

    Automation platforms let you add conditional branches: if this, then that. But most platforms treat skipped steps as successful. If a condition evaluates to false and a step doesn’t run, the workflow continues as if nothing went wrong.

    This becomes a problem when you assume a step always runs. You build a workflow that creates a new client folder in Google Drive, then sends a welcome email. You add a condition: only create the folder if the client type is “Enterprise.” You forget to update the email step. Now every non-Enterprise client gets a welcome email with a broken link to a folder that doesn’t exist.

    Example: You’re using Make to process course payments. Step one checks if the payment amount is above $500. If yes, it tags the customer as “VIP” in your CRM. Step two sends a welcome email that references VIP perks. The condition on step one fails for a $499 purchase, so the tag never applies—but the email still sends, promising benefits the customer doesn’t have.

    How to catch it: Never branch conditional logic unless you explicitly handle both paths. If a step should always run, don’t nest it inside a conditional. If you must branch, add a final step at the end of each path that logs which branch executed. Route all branches to a single “merge” step that checks for the log entry. If it’s missing, trigger an alert.

    Webhooks that timeout without retry

    Webhooks are HTTP requests that external services send to your automation platform when an event happens—new form submission, payment received, file uploaded. Most webhook receivers have a timeout: if your automation doesn’t respond within 5–30 seconds, the sender assumes it failed and moves on.

    Some services retry failed webhooks. Most don’t. If your automation is slow—waiting for an API call, processing a large file, looping through a list—it might not respond before the timeout. The webhook sender logs the delivery as failed and never tries again. Your automation never runs.

    Example: You’re using n8n to trigger a workflow when someone submits a Typeform. The workflow checks their email against your CRM, adds tags based on their answers, then sends a confirmation email. The CRM lookup takes 8 seconds. Typeform’s webhook timeout is 5 seconds. The webhook fails, but Typeform doesn’t retry. The submitter never gets a confirmation, and you never know they filled out the form.

    How to catch it: Split long-running workflows into two parts. The first workflow receives the webhook, immediately responds with a 200 OK, and writes the payload to a temporary data store (Airtable, Google Sheets, a database). The second workflow polls the data store every minute, processes pending items, and marks them complete. If a webhook times out, the first workflow still saves the data, and the second workflow picks it up on the next poll.

    Build failure detection into every workflow

    Automation platforms optimize for speed and ease of use. They don’t optimize for reliability. If you’re running a business on top of Zapier, Make, or n8n, you need to assume every workflow will eventually fail—and build your own safety net.

    Add logging. Add confirmation checks. Add alerts. Add fallback paths. Treat every external API call as potentially flaky. Treat every conditional branch as a place where data might vanish. Treat every webhook as a delivery that might never arrive.

    The 10 extra minutes you spend hardening a workflow will save you hours of debugging when it breaks at 2 a.m. on a Saturday.

    Want more breakdowns like this? Subscribe to One Two Three Send and get operator-focused tool guides, platform deep dives, and workflow teardowns in your inbox twice a 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.

  • What every paid traffic dashboard hides about cost-per-click timing

    What every paid traffic dashboard hides about cost-per-click timing

    What every paid traffic dashboard hides about cost-per-click timing
    Photo: André Karwath aka Aka via Wikimedia Commons (CC BY-SA 2.5)

    Most solo operators running paid traffic check their dashboard, see a cost-per-click figure, and make budget decisions based on it. What they don’t realize: that number is often wrong—not because the platform is lying, but because cost data and click data don’t arrive at the same time.

    This timing gap matters more than you think. It affects how you read early campaign performance, when you pause underperforming ads, and whether you’re actually spending what you think you are.

    Cost data lags behind click data

    When someone clicks your ad, the platform records the click instantly. It shows up in your dashboard within seconds to minutes, depending on the platform’s refresh cycle.

    But the cost associated with that click? That’s processed separately. Ad platforms batch-process billing events—grouping clicks, impressions, and conversions into billing cycles that settle every few hours. Google Ads typically processes cost data every 3–6 hours. Meta Ads runs similar cycles, though the exact timing isn’t published.

    What this means: if you check your dashboard 20 minutes after launching a campaign, you might see 50 clicks and a CPC of $0.00. Not because the clicks are free, but because the cost data hasn’t posted yet.

    The inverse also happens. You pause a campaign, and the cost-per-click jumps an hour later as delayed billing data catches up. You didn’t suddenly get more expensive clicks—you’re just seeing the settlement lag.

    Why early CPC numbers mislead you

    This lag creates a specific problem for operators who optimize quickly. If you launch three ad variations, check performance after 30 minutes, and pause the one with the highest CPC, you’re making that decision on incomplete data.

    Let’s say Ad A shows 40 clicks at $1.20 CPC, and Ad B shows 35 clicks at $0.85 CPC. You pause Ad A. Three hours later, the cost data settles: Ad A’s true CPC was $0.95, and Ad B’s was $1.40. You paused the wrong one.

    This isn’t a hypothetical edge case. It happens every time you make budget decisions faster than the billing cycle completes. The smaller your sample size and the shorter your observation window, the more the lag distorts your read.

    When the timing gap matters most

    Not every campaign is affected equally. If you’re running a stable campaign with thousands of clicks per day, the lag smooths out—yesterday’s delayed costs blend into today’s totals, and your rolling averages stay accurate.

    But if you’re testing new creatives, launching small-budget experiments, or running short promotional windows, the lag is your biggest blind spot. Here’s when to watch for it:

    • First 6 hours of any new campaign. CPC figures are unreliable until at least one full billing cycle completes. Don’t pause or scale based on early numbers.
    • Low daily budgets under $50. Small absolute spend means every delayed cost event skews your percentage read. A $12 cost posting late can swing your apparent CPC by 40%.
    • End-of-day budget checks. If you check at 11 PM to see if you hit your daily cap, remember: the last few hours of cost data might not be there yet. You might wake up over budget.

    How to work around it

    You can’t eliminate the lag, but you can structure your workflow to avoid bad decisions:

    Wait at least 8 hours before making major changes. Give the cost data time to settle. If you’re testing three ads, let them run overnight before you pause anything. Your initial read will be incomplete.

    Use cumulative metrics, not session snapshots. Don’t compare CPC from your morning check to your afternoon check. Compare yesterday’s final totals to today’s final totals. The lag affects point-in-time reads, not closed-day totals.

    Download raw reports instead of reading the dashboard. Most platforms let you export click and cost data with timestamps. If you’re troubleshooting a discrepancy, the export will show you which costs posted late. The dashboard just gives you the blended average.

    Set budget caps 10–15% below your true limit. If you absolutely can’t spend more than $100 in a day, set the platform cap at $85. That gives you a cushion for delayed cost posting. You won’t wake up $20 over.

    One more thing: refunds and adjustments lag even longer

    Cost data settling in 3–6 hours is the normal case. But if the platform later flags a click as invalid—bot traffic, accidental double-click, policy violation—the refund can take 24–72 hours to post.

    This means your CPC might drop two days after a campaign ends, as the platform claws back fraudulent costs. It’s rare, but it happens often enough that you shouldn’t finalize your campaign ROI analysis the same day you pause it. Give it 48 hours to fully settle.

    The short version: paid traffic dashboards show you two different data streams—clicks and costs—on two different schedules. If you treat the blended number as real-time truth, you’ll make decisions on incomplete information. Wait for the billing cycle. Check cumulative totals. And don’t trust early CPC reads.

    Got a workflow question about running paid traffic as a solo operator? Reply to this email—I read every one.