Author: onetwothreeadmin

  • Google Analytics 4’s IP exclusion filter and why it still logs you

    Google Analytics 4’s IP exclusion filter and why it still logs you

    You add your office IP to Google Analytics 4’s exclusion filter, click save, and assume your internal visits stop polluting your traffic reports. Then you check a few days later and your test sessions are still showing up—or worse, they disappear from one report but linger in another.

    GA4’s IP exclusion doesn’t work the way Universal Analytics did. It doesn’t reject traffic at the door. It logs everything, applies a filter tag, and suppresses matching hits in most—but not all—standard reports. That distinction matters when you’re trying to get clean data or debug why your numbers don’t match what you see in server logs.

    How GA4’s IP filter actually works

    When you define an IP exclusion filter in GA4’s data stream settings, you’re not blocking requests. You’re telling Google to apply an internal_traffic parameter to any hit that matches your IP range. That parameter gets attached to the event payload and flows into BigQuery exports and debug logs, but it’s excluded from most GA4 interface reports by default.

    The key word is “most.” Explorations, custom funnels, and any report built with unfiltered dimensions can still surface internal traffic if you don’t manually apply a segment or filter. Real-time reports often show excluded IP hits for several minutes before suppression kicks in, because the tagging happens asynchronously.

    If you’re running a low-traffic site and testing conversion flows yourself, a single internal session can skew daily conversion rates by 20% or more—even if you think you’ve filtered it out.

    Why your IP still appears in some reports

    Three common reasons:

    • Dynamic IP assignment: If your ISP rotates your IP every few days, your exclusion rule goes stale. GA4 doesn’t notify you when this happens. You have to check your public IP periodically and update the filter manually, or use a broader CIDR range if your provider allocates from a known block.
    • Mobile and VPN traffic: Your phone’s carrier IP and any VPN endpoint won’t match your home or office range. If you test email links on mobile or browse your site through a VPN, those sessions aren’t tagged as internal unless you’ve added every possible IP.
    • Server-side GTM or Measurement Protocol hits: If you’re sending events via server-side Google Tag Manager or the GA4 Measurement Protocol, the IP GA4 sees is your server’s IP, not the end user’s. Your exclusion filter won’t match unless you explicitly pass the user’s IP in the payload and your server is on the excluded list.

    When to use IP exclusion vs. other filtering methods

    IP filtering works well for small teams working from fixed locations—a home office, a coworking space with a static IP, or a single cloud server sending backend events. It’s lightweight, doesn’t require changing your tagging code, and applies retroactively to all future hits.

    But if your team is distributed, remote, or frequently mobile, IP exclusion becomes a maintenance burden. A better approach: set a first-party cookie or localStorage flag when someone logs into your CMS or admin panel, then use Google Tag Manager to check for that flag and not fire the GA4 tag at all. This method blocks the hit entirely rather than tagging and suppressing it.

    For agencies or consultants who work across dozens of client sites, browser extensions like “Block Yourself from Analytics” (available for Chrome and Firefox) inject a query parameter or disable the GA4 script on sites you specify. You don’t have to ask every client to add your IP to their exclusion list.

    One non-obvious gotcha

    GA4’s IP exclusion filter doesn’t apply to Google Signals data or any cross-device user stitching. If you’re logged into a Google account on your work machine and browse your own site, Google may still associate that session with your broader user profile for audience building and attribution modeling, even though the session is tagged as internal traffic. The data won’t appear in your standard reports, but it can influence remarketing audiences and conversion credit if Google Signals is enabled.

    To fully exclude yourself from tracking and modeling, you need to either disable Google Signals in your property settings or browse in an incognito window while logged out of all Google accounts.

    IP exclusion in GA4 is not a firewall—it’s a post-collection label. If you need truly clean data and you’re testing frequently, combine IP filtering with client-side tag blocking and regular audits of your Realtime and Exploration reports. And if your IP changes more than once a month, automate the filter update or switch to a cookie-based exclusion method before your internal traffic becomes your largest segment.

    What’s your internal-traffic filtering setup? Reply and let us know what’s working—or what’s leaking through.

  • What Gumroad’s ‘pay what you want’ pricing actually earns you

    What Gumroad’s ‘pay what you want’ pricing actually earns you

    Gumroad’s pay-what-you-want (PWYW) pricing looks like a generous experiment until you run the numbers. The feature lets buyers name their own price above a floor you set—or no floor at all. The pitch is that generosity builds goodwill, increases conversions, and unlocks buyers who’d never pay full price.

    The reality is messier. PWYW can work, but only in narrow conditions. Most operators who flip the switch see average transaction values drop between 40% and 70% compared to fixed pricing, even when they set a suggested price. The question isn’t whether PWYW lowers your per-sale revenue—it does—but whether the volume increase offsets the loss.

    What buyers actually pay when you let them choose

    Three anonymised operators shared their Gumroad dashboard data after running PWYW experiments on digital products priced between $15 and $49. Here’s what happened:

    • Operator A: A $29 Notion template. Fixed price averaged $29 (obviously). PWYW with a $15 floor and $29 suggestion averaged $18.40 over 200 transactions. Conversion rate increased 22%, but total revenue dropped 18%.
    • Operator B: A $49 course bundle. PWYW with no floor and a $49 suggestion averaged $12.60 over 180 sales. Conversion rate doubled, revenue increased 9%. The operator considered it a win but switched back after realising most new buyers never purchased again.
    • Operator C: A $15 guide. PWYW with a $10 floor averaged $11.30. Conversion rate increased 8%, revenue dropped 31%. Switched back to fixed pricing within two weeks.

    The pattern: PWYW reliably increases conversion rate, but average order value collapses. The floor matters—Operator A’s $15 minimum kept the average above $18, while Operator B’s lack of a floor invited $5 and $7 payments on a product originally priced at $49.

    When PWYW makes sense (and when it doesn’t)

    PWYW works best as a short-term acquisition tool, not a permanent pricing strategy. The clearest use case is launching a new product to an untested audience. You’re optimising for feedback and social proof, not revenue. A $0 floor with a reasonable suggestion—say, $19 on a product you plan to price at $39—gets you early buyers, testimonials, and usage data you can fold into the final version.

    It also works for products with near-zero marginal cost and high repeat-purchase potential. If you’re selling a lightweight template or checklist and you have a clear upsell or backend offer, PWYW can fill the top of your funnel cheaply. But if the product is your only offer, or if you’re selling something that required weeks of work to build, PWYW usually just trains your audience to expect discounts.

    Where it fails: evergreen products with established audiences. If people already know your work and trust your pricing, PWYW doesn’t increase conversions meaningfully—it just gives existing buyers permission to pay less. Operator C’s 8% conversion lift didn’t justify a 31% revenue drop because the audience was already warm.

    The non-obvious cost: anchoring your own pricing

    The hidden risk of PWYW is psychological, not financial. Once you’ve let buyers pay $10 for something you later price at $39, you’ve anchored their perception of value. If they see the same product—or something similar—at the higher price later, they’ll assume you’re overcharging, not that they got a deal earlier.

    This is particularly painful if you run PWYW as a launch discount and then switch to fixed pricing. The buyers who paid $8 will tell others they paid $8. The new buyers who see $39 will Google your product, find Reddit threads or tweets mentioning the lower price, and wait for another sale. You’ve effectively turned a one-time experiment into a permanent discount expectation.

    The fix: if you use PWYW, frame it explicitly as a beta, a launch window, or a limited-time test. Set a public end date and stick to it. Don’t let it drift into your evergreen offer.

    Gumroad’s PWYW settings: what to toggle

    Gumroad gives you three levers: minimum price, suggested price, and whether to show the suggestion. The minimum is a hard floor—buyers can’t pay less. The suggested price is what Gumroad displays in the checkout field by default. If you hide the suggestion, the field is blank and buyers have to decide with no anchor.

    Showing the suggestion raises the average payment by 30–50% compared to hiding it, based on the data above. Operator B’s $12.60 average with a $49 suggestion would likely have dropped below $10 with no suggestion shown. If you’re going to use PWYW, always show a suggestion—and set it at 60–70% of your intended fixed price, not 100%. A $49 suggestion on a product you plan to sell for $49 just makes buyers feel like they’re being manipulated into paying full price anyway.

    The minimum should be at least 30–40% of your target price, unless you’re explicitly using PWYW as a free-plus-reputation play. Operator A’s $15 floor on a $29 product kept the average at $18.40. Operator B’s $0 floor tanked the average to $12.60 on a $49 product. Floors work.

    Want more pricing breakdowns, tool comparisons, and operator data? Subscribe to One Two Three Send and get one article like this every day.

  • WordPress admin-ajax.php: what’s hammering your server and how to fix it

    WordPress admin-ajax.php: what’s hammering your server and how to fix it

    If you’ve ever looked at your WordPress server logs or run a performance profiler, you’ve probably seen admin-ajax.php appear hundreds—sometimes thousands—of times per minute. It’s not a bug. It’s a feature. But it’s also one of the most common reasons WordPress sites slow to a crawl under moderate traffic.

    Understanding what admin-ajax.php does, why it becomes a problem, and how to fix it without breaking your site is essential if you’re running a content site that depends on uptime and speed.

    What admin-ajax.php actually does

    WordPress uses admin-ajax.php to handle AJAX requests—asynchronous HTTP calls that let plugins and themes update parts of a page without reloading the whole thing. It’s the same file whether you’re logged in or not, and it handles everything from form submissions to live search to analytics pings.

    Every time a plugin needs to check something in the background—whether that’s a cart update, a like button, a notification counter, or a tracking pixel—it often routes through admin-ajax.php. The file itself is lightweight. The problem is what gets triggered after the request hits it.

    Each admin-ajax.php call loads the entire WordPress core, plus every active plugin, even if the request only needs one function from one plugin. That’s expensive. If you’re getting fifty admin-ajax.php calls per page load, you’re essentially booting WordPress fifty times per visitor.

    How to see if it’s a problem on your site

    Install Query Monitor (it’s free). Load a few pages on your site while logged out. Check the “AJAX” panel. If you see dozens of requests firing on every page, or if the same hook is being called repeatedly, you’ve got a problem.

    Another test: open your browser’s network inspector (F12 → Network tab). Load your homepage. Filter by “admin-ajax.php.” Count how many requests fire. Anything above five is worth investigating. Anything above twenty is a red flag.

    Common culprits include:

    • Social sharing plugins that ping counters on every page load
    • Live chat widgets checking for new messages every few seconds
    • Analytics plugins logging events in real time
    • WooCommerce or membership plugins polling session data
    • Page builders with “live edit” preview modes running in the background

    Three ways to reduce the load

    1. Replace the plugin. If a plugin is hammering admin-ajax.php and there’s a leaner alternative, switch. Social share counters are a classic example—most don’t need live data. Cache the counts once per hour and serve static numbers.

    2. Disable unnecessary AJAX calls. Many plugins let you turn off real-time features. WooCommerce, for example, has a “cart fragments” feature that updates the cart icon via AJAX on every page. If you don’t show a persistent cart widget, you can disable it with a one-line code snippet:

    add_action('wp_enqueue_scripts', function() { wp_dequeue_script('wc-cart-fragments'); }, 11);

    That single change can cut admin-ajax.php requests by 70% on WooCommerce sites.

    3. Move AJAX calls to the REST API. If you control the plugin or theme code, rewrite AJAX handlers to use WordPress’s REST API instead of admin-ajax.php. REST endpoints don’t load the admin environment, so they’re faster and easier to cache. This requires developer work, but it’s the cleanest long-term fix.

    When caching makes it worse

    Most page caching plugins don’t cache admin-ajax.php by default, because the responses are often user-specific. That means every AJAX call bypasses your cache and hits PHP directly.

    If your site is getting 10,000 page views per day and each page fires 15 admin-ajax.php requests, that’s 150,000 uncached PHP executions. Your server will feel it.

    Some hosts—particularly managed WordPress hosts—rate-limit admin-ajax.php to prevent abuse. If you hit that limit, parts of your site just stop working. Forms don’t submit. Buttons don’t respond. Visitors leave.

    The fix isn’t to increase the limit. It’s to reduce the requests.

    One non-obvious tip

    If you’re stuck with a plugin that won’t stop calling admin-ajax.php, and you can’t replace it, consider splitting the plugin’s functionality onto a separate subdomain or a headless API. For example, if you’re running a live chat widget that pings admin-ajax.php every three seconds, host the chat on chat.yoursite.com and let it hit a lightweight Node.js endpoint instead of your WordPress server.

    It’s more work up front, but it isolates the performance hit and keeps your main site fast.

    What’s slowing down your WordPress site right now? Reply with your biggest performance headache—I’ll cover it in a future issue. And if you’re shopping for a host that understands this stuff out of the box, BigScoots is worth a look.

  • AI image generators bill you per API call—here’s the math

    AI image generators bill you per API call—here’s the math

    Most solo operators experimenting with AI image generation start with a web interface—DALLĀ·E’s playground, Midjourney’s Discord bot, or Stability AI’s DreamStudio. The pricing feels simple: buy credits, burn through them, top up when you run out.

    Then you scale. You want to automate thumbnail generation for blog posts, create social assets in bulk, or build a tool that generates images on demand. Suddenly you’re looking at API pricing, and the math gets complicated fast.

    Here’s what actually happens when you move from casual generation to programmatic use, with real numbers from three major platforms.

    DALLĀ·E 3: pay per resolution and quality tier

    OpenAI’s DALLĀ·E 3 API charges based on image resolution and quality setting. As of May 2026, standard quality at 1024Ɨ1024 costs $0.040 per image. HD quality at the same resolution jumps to $0.080. If you drop to 1024Ɨ1792 (portrait or landscape), HD pricing climbs to $0.120 per image.

    That means 1,000 standard blog thumbnails cost $40. If you want HD quality for each, that’s $80. For a daily newsletter with five images per issue, you’re looking at $1.20 per send in HD, or $730 per month if you publish Monday through Friday.

    DALLĀ·E 3 doesn’t offer volume discounts. You pay the same rate whether you generate ten images or ten thousand. The API is fast—typically under ten seconds per generation—but there’s no batch pricing, no prepaid tiers, and no way to lock in a lower rate.

    Midjourney: seat-based pricing, not per-image

    Midjourney doesn’t sell API access the way OpenAI does. Instead, you subscribe to a plan that gives you a monthly GPU time allowance. The Basic plan costs $10/month for roughly 200 images (about 3.3 hours of GPU time). The Standard plan is $30/month for around 900 images (15 hours). Pro runs $60/month for 1,800 images (30 hours), with an option to buy additional GPU hours at $4 per hour.

    If you’re automating image generation, Midjourney’s Discord-first architecture creates friction. There’s no official REST API yet. Third-party wrappers exist, but they scrape the Discord bot and risk rate limits or account suspension. For reliable programmatic use, Midjourney isn’t viable—even though the per-image cost on a Standard plan works out to about $0.033, cheaper than DALLĀ·E 3.

    Stable Diffusion: self-hosting vs. hosted APIs

    Stable Diffusion is open-source, which changes the cost structure entirely. You can run it locally or on your own cloud instance, paying only for compute. A mid-tier GPU instance on AWS (g5.xlarge with an NVIDIA A10G) costs around $1.006 per hour on-demand. If you generate 100 images per hour, that’s roughly $0.01 per image—75% cheaper than DALLĀ·E 3 standard quality.

    But self-hosting requires setup: installing dependencies, managing model weights, handling queues, and monitoring uptime. For solo operators generating fewer than 500 images a month, the overhead usually isn’t worth it.

    Hosted Stable Diffusion APIs solve this. Stability AI’s own API charges $0.01 per image for SDXL (1024Ɨ1024). Replicate offers SDXL at $0.0055 per image, billed per compute second. Both are significantly cheaper than DALLĀ·E 3, but image quality and prompt adherence vary more widely. You’ll burn extra generations refining prompts.

    Hidden costs: retries, storage, and moderation

    Every AI image API occasionally returns unusable output—cropped faces, garbled text, or results that ignore your prompt entirely. DALLĀ·E 3 is the most reliable, but you’ll still retry 5–10% of generations. Stable Diffusion can require three or four attempts to get a usable image, especially with complex prompts.

    Factor retries into your budget. If your effective cost per usable image is 1.2Ɨ the API’s listed price, a $0.01 Stable Diffusion call becomes $0.012. A $0.04 DALLĀ·E call becomes $0.048.

    Storage adds up too. A single 1024Ɨ1024 PNG averages 1.5–2 MB. Generate 10,000 images and you’re storing 20 GB. At $0.023/GB/month on AWS S3, that’s $0.46/month—not huge, but it scales linearly. If you’re generating images for a public-facing tool, you’ll also need a CDN. Cloudflare’s free tier works for light use; beyond that, budget $0.01–0.02 per GB transferred.

    Content moderation is another variable cost. DALLĀ·E 3 includes built-in filtering, but Stable Diffusion doesn’t. If you’re accepting user prompts, you’ll need a moderation layer—either OpenAI’s moderation endpoint ($0.0001 per request) or a third-party service like Sightengine, which starts at $39/month for 5,000 images.

    When self-hosting makes sense

    Self-hosting Stable Diffusion pays off when you’re generating more than 2,000 images per month and can batch them efficiently. Spin up a GPU instance, queue 500 generations, process them in parallel, then shut the instance down. You’ll pay for an hour or two of compute instead of thousands of individual API calls.

    For sporadic use—ten images one day, none for a week—stick with a hosted API. The convenience premium is worth it.

    If you’re choosing between DALLĀ·E 3 and Stable Diffusion APIs, run a quality test first. Generate twenty images with identical prompts on both platforms. If DALLĀ·E 3 nails the prompt 90% of the time and Stable Diffusion needs three tries per usable image, DALLĀ·E’s 4Ɨ higher price might still be cheaper per good output.

    Want more breakdowns like this? Subscribe to One Two Three Send for weekly operator-focused analysis of tools, pricing, and infrastructure decisions.

  • Pinterest analytics: traffic numbers you can’t trust

    Pinterest analytics: traffic numbers you can’t trust

    Pinterest reports “impressions” that never reach a human eye, counts “outbound clicks” that bounce before your page loads, and attributes traffic to pins that expired months ago. If you’re using Pinterest analytics to measure content performance or justify ad spend, you’re working with numbers that don’t match reality.

    The platform’s dashboard looks authoritative—graphs climb, engagement rates trend upward—but the definitions underneath those charts don’t align with how any other analytics tool counts traffic. Here’s what’s actually being measured, where the gaps appear, and how to build a reconciliation workflow that survives Pinterest’s reporting quirks.

    Impressions include bots, pre-fetches, and feed scrolls

    Pinterest defines an “impression” as any time a pin appears in a feed, search result, or related-pins sidebar. It doesn’t require the pin to be visible on-screen for any minimum duration, and it doesn’t filter out automated crawlers or pre-fetch requests from mobile apps.

    In practice, this means your impression count includes:

    • Pins that loaded below the fold while a user scrolled past without stopping
    • Feed positions that rendered during a bot scrape or API call
    • Pins served to users who immediately closed the app or tab
    • Pre-cached pins on mobile devices that never displayed

    The gap between Pinterest impressions and actual human attention is usually 40–60%. A pin with 10,000 impressions might have been meaningfully viewed by 4,000–6,000 people. Pinterest doesn’t offer a “viewable impressions” filter, so you can’t isolate the subset that matters.

    Outbound clicks fire before your page loads

    When a user taps a pin, Pinterest logs an “outbound click” immediately—before your landing page starts to load and before the user sees your content. If the page takes more than two seconds to render, or if the user taps the back button during load, Pinterest still counts the click.

    Compare Pinterest’s outbound-click count to Google Analytics (or Plausible, or Fathom) pageviews for the same URL over the same date range. The mismatch is typically 20–35%. Pinterest reports more clicks than your analytics tool records as visits.

    Common causes:

    • Slow server response times (anything above 1.5 seconds)
    • Mobile users on flaky connections who abandon mid-load
    • Accidental taps that are immediately reversed
    • Referrer-stripping privacy tools that block your analytics script

    None of this makes Pinterest dishonest—it’s just measuring click intent, not completed pageviews. But if you’re calculating cost-per-visitor for Pinterest ads or trying to attribute conversions, the numerator and denominator come from incompatible datasets.

    Attribution windows extend six months into the past

    Pinterest attributes a click to the original pin, even if that pin was saved, re-pinned, or shared weeks earlier. The platform’s attribution window runs up to 180 days for organic pins and 30 days for promoted pins.

    If someone saved your pin in January, forgot about it, then clicked through in May, Pinterest’s May traffic report will show that click—but your Google Analytics source/medium will say “pinterest.com / referral” with no way to trace it back to the original pin or board.

    This creates two problems:

    • You can’t isolate which current pins are driving traffic today
    • Old pins continue to generate attributed clicks long after you’ve moved on to new content

    The workaround: append UTM parameters to every pin link, using the pin creation date or a unique pin ID in the utm_content field. That lets you reconcile Pinterest’s attributed clicks with your analytics tool’s campaign reports, even when the platform’s dashboard lumps everything together.

    Building a reconciliation workflow

    You need three numbers to make Pinterest traffic actionable:

    Reported outbound clicks (from Pinterest analytics) → Landed pageviews (from your analytics tool, filtered to pinterest.com referrer) → Conversions (email signups, purchases, or whatever you’re optimising for).

    Export Pinterest’s top-pins report weekly. Pull the same date range from your analytics dashboard, filtered to Pinterest referral traffic. Join the two datasets on UTM parameters (or manually, if you’re only tracking a handful of pins). Calculate the click-to-pageview ratio and the pageview-to-conversion rate separately.

    Most operators see:

    • 70–80% of outbound clicks turn into pageviews (higher is better; investigate load times if you’re below 65%)
    • 2–8% of pageviews convert, depending on offer and audience temperature

    Track those ratios over time. If Pinterest impressions climb but your click-to-pageview ratio drops, your pins are reaching the wrong audience or your landing page is too slow. If pageviews hold steady but conversions fall, the problem isn’t Pinterest—it’s your offer or page copy.

    Pinterest’s dashboard won’t tell you any of this. You have to build the reconciliation layer yourself, and you have to remember that the platform’s numbers are always an optimistic upper bound. Plan around the pageviews that land, not the clicks Pinterest says it sent.

    Using Pinterest to drive real traffic? Subscribe to One Two Three Send for weekly breakdowns of the tools, metrics, and workflows that solo operators actually use—no fluff, no generic advice.

  • ConvertKit vs. MailerLite: which ESP fits a sub-5,000 list

    If you’re running a content business with fewer than 5,000 subscribers, you’re in the sweet spot where platform choice actually matters. Pick wrong and you’ll either overpay for features you don’t use or outgrow your tool in six months.

    ConvertKit and MailerLite both target solo operators and small teams, but they solve different problems. Here’s what each does well, where each falls short, and who should pick which.

    Pricing: where the gap widens

    MailerLite’s free tier covers up to 1,000 subscribers and includes automation, landing pages, and a website builder. You’ll pay $9/month for 1,000–2,500 subscribers, $18/month for 2,500–5,000.

    ConvertKit starts at $25/month for up to 1,000 subscribers. At 2,500 subscribers you’re paying $41/month; at 5,000 it’s $66/month. There’s a free tier capped at 300 subscribers, but it strips out automation—the main reason to use ConvertKit in the first place.

    If budget is tight and you’re just starting, MailerLite saves you $300–$600/year at the same list size. ConvertKit’s pricing assumes you’re monetising early or plan to.

    Automation: depth vs. simplicity

    ConvertKit’s visual automation builder lets you branch, tag, delay, and score subscribers based on link clicks, form submissions, product purchases, and custom events. You can build sequences that feel like decision trees. It’s overkill if you’re just sending a weekly digest, but essential if you’re running a paid community, a course funnel, or segmented content tracks.

    MailerLite’s automation is lighter. You get triggers, delays, conditions, and basic branching. It handles welcome sequences, re-engagement flows, and simple product funnels without friction. But once you need multi-step logic—like “if they clicked this link but didn’t buy, tag them and send a different sequence”—you’ll hit the ceiling fast.

    Non-obvious tip: MailerLite’s workflow editor saves every change instantly. ConvertKit requires you to manually activate automations after editing. That’s a feature, not a bug—it prevents you from accidentally breaking a live sequence. But it also means you need to remember to turn things back on.

    Forms, landing pages, and creator-focused extras

    Both platforms include landing pages and signup forms. MailerLite’s templates look cleaner out of the box and load faster. ConvertKit’s forms integrate tightly with its tagging system, so you can pre-segment subscribers at signup without Zapier.

    ConvertKit also includes a commerce layer—you can sell digital products, subscriptions, and tip jars directly through the platform. It takes a 3.5% + $0.30 transaction fee on top of Stripe’s cut, but it’s built in. MailerLite doesn’t offer native e-commerce; you’ll need to connect Gumroad, Stripe Checkout, or a course platform.

    If you’re monetising through paid newsletters or digital products, ConvertKit’s commerce tools save you from duct-taping three services together. If you’re running ads, affiliates, or sponsorships, MailerLite’s lower base cost matters more.

    Deliverability and reporting

    Both platforms maintain strong sender reputations and handle SPF/DKIM setup for you. Deliverability differences at this scale come down to list hygiene, not platform choice.

    ConvertKit’s reporting is subscriber-centric: you can see every action a single subscriber took across broadcasts, automations, and landing pages. MailerLite’s reporting is campaign-centric: opens, clicks, unsubscribes per send. ConvertKit’s view is better for understanding individual journeys; MailerLite’s is faster for diagnosing a bad campaign.

    Who should pick which

    Choose MailerLite if you’re pre-revenue, sending one or two emails per week, and need to keep costs under $20/month. It’s also the better pick if you value design flexibility and don’t need multi-step conditional logic.

    Choose ConvertKit if you’re already monetising, running segmented content tracks, or plan to sell directly through email. The automation depth and commerce tools justify the higher price once you’re past the “is this working?” phase.

    For most operators under 5,000 subscribers, the decision comes down to one question: are you optimising for cost or for automation depth? MailerLite wins the first; ConvertKit wins the second. Neither is a bad choice—just different bets on where your business is headed.

    Want more tool breakdowns like this? Subscribe to One Two Three Send and get honest comparisons, operator-focused tutorials, and no-fluff guidance every week.

  • Social proof widgets slow your site more than they boost conversions

    Social proof widgets slow your site more than they boost conversions

    Social proof widgets—those little popup notifications that tell you “Mike from Austin just signed up” or “127 people are viewing this page”—promise to increase conversions by showing real-time activity. Most operators install them hoping for a 5–10% lift in signups or sales.

    The math rarely works out. These widgets typically add 300–800ms to your page load time, and Google’s Core Web Vitals penalize anything that shifts layout or delays interactivity. For content-driven businesses where most revenue comes from return visits and organic search, that performance cost outweighs the marginal conversion bump.

    What these widgets actually cost

    A typical social proof script—Proof, Fomo, UseProof, or TrustPulse—loads between 45KB and 120KB of JavaScript. That’s on top of your analytics, email capture forms, and content delivery.

    On a midrange mobile connection (4G, not 5G), that adds roughly 400ms to your First Contentful Paint and another 200–400ms to Time to Interactive. If you’re running WordPress on shared hosting without a CDN, it’s worse: 600–1,000ms is common.

    Google’s algorithm treats anything above 2.5 seconds for Largest Contentful Paint as “poor.” If your page was already sitting at 2.1 seconds, a social proof widget pushes you into the penalty zone. The organic traffic loss from a rankings drop often exceeds any conversion lift the widget provided.

    The conversion lift is smaller than advertised

    Vendor case studies claim 10–15% conversion increases. Independent A/B tests from operators I’ve spoken with show 2–4% lifts—and only on cold traffic landing pages, not on content pages or repeat-visitor flows.

    If you’re running a newsletter signup page that converts at 8%, a 3% relative lift gets you to 8.24%. That’s 2.4 extra signups per 1,000 visitors. Useful, but not transformative—and only if the widget doesn’t tank your traffic by hurting SEO.

    For operators whose revenue comes primarily from content SEO and repeat readers (newsletters, affiliate blogs, course creators with organic funnels), protecting page speed and search rankings matters more than squeezing another percentage point from cold landing-page visitors.

    When social proof actually works

    Social proof widgets make sense in three scenarios:

    • Paid-traffic landing pages where you control the source and every visitor is cold. You’re not relying on SEO, so the performance hit doesn’t cost you rankings.
    • High-ticket product pages where the conversion value justifies the engineering cost. If one extra sale per week is worth $500+, the trade-off pencils out.
    • Launch campaigns with short, time-bound traffic spikes. Install the widget for two weeks, capture the momentum signal, then remove it.

    If you’re running a content site where 60%+ of traffic is organic and repeat, static testimonials and subscriber counts perform nearly as well without the JavaScript overhead.

    Better alternatives that don’t slow you down

    Instead of a live widget, try:

    • Static testimonial blocks in your sidebar or above the fold. One sentence, a name, and a photo. No external script required.
    • Subscriber count in your header or opt-in copy. “Join 12,000 operators” works as social proof and costs zero milliseconds.
    • Occasional email broadcasts highlighting recent wins or community size. You control the message and don’t sacrifice site performance.

    If you’re committed to live notifications, lazy-load the script so it only fires after the primary content renders. Most platforms don’t offer this out of the box—you’ll need a developer or a plugin like WP Rocket’s delay-JavaScript feature.

    For most solo operators and small teams, the simplest move is to skip the widget entirely. Your site will load faster, your SEO won’t take a hit, and your conversion rate will stay within a percentage point of what the widget promised.

    If this helped: reply and tell me which performance tool you’re using to audit your site. I’m tracking what operators actually rely on versus what gets recommended in generic listicles.

  • Stop chasing every algorithm update—SEO traffic thrives on inertia

    Stop chasing every algorithm update—SEO traffic thrives on inertia

    Every time Google releases a core update, the same panic cycle starts. Traffic drops fifteen percent overnight, operators scramble to “fix” their content, and Twitter fills with theories about what changed. Two weeks later, rankings drift back toward baseline, and everyone pretends the emergency never happened.

    The pattern repeats because we’ve been trained to believe every algorithm shift demands immediate action. It doesn’t. Most organic traffic operates on inertia—what worked last quarter keeps working unless you fundamentally break something. The operators who treat SEO like a stable system outperform the ones who chase every fluctuation.

    Algorithm updates resolve themselves more often than not

    Google’s core updates typically roll out over two weeks. Rankings swing during that window because the algorithm is actively re-evaluating millions of pages. If your traffic drops on day three of a rollout, you’re watching the system recalibrate in real time—not receiving a verdict on your content quality.

    Data from Search Engine Journal’s analysis of the March 2024 core update showed that 60% of sites that lost visibility in the first week recovered at least half of it by day fourteen. Another 20% saw full recovery within thirty days. The sites that stayed down had pre-existing issues: thin content, aggressive ad layouts, or domain-level trust problems that no amount of emergency editing would fix.

    Reacting during the rollout window wastes time twice. First, you’re optimizing against incomplete data. Second, you’re often “fixing” pages that will recover on their own, which means you’ll never know whether your changes helped or whether the algorithm simply finished its work.

    What actually breaks SEO traffic

    Sustainable ranking losses come from operator decisions, not algorithm updates. The most common culprits:

    • Site migrations that lose redirects. Moving from one CMS to another, switching domains, or restructuring URLs without proper 301 mappings will kill traffic faster than any core update. Google can’t rank pages it can’t find.
    • Aggressive monetization layered onto existing content. Adding interstitials, auto-play video ads, or affiliate blocks that push the main content below the fold triggers Core Web Vitals penalties and user-experience downgrades. These don’t recover automatically.
    • Months of no new content. Google’s freshness signals favor sites that publish regularly in their niche. A six-month publishing gap signals abandonment, and rankings drift accordingly. This isn’t an algorithm penalty—it’s atrophy.
    • Competitor momentum. If three competitors publish better-structured content on your core topics while you’re frozen in algorithm-panic mode, they’ll outrank you. The update didn’t hurt you; standing still did.

    These issues share a trait: they’re cumulative and structural. You can measure them, fix them, and prevent recurrence. Algorithm updates, by contrast, are external and non-actionable during the rollout window.

    The inertia strategy: monthly audits, not daily firefighting

    Operators who treat SEO traffic as stable infrastructure check four things once a month, not once a day:

    1. Crawl health. Run Screaming Frog or Sitebulb against your top fifty pages. Look for new 404s, redirect chains longer than two hops, or orphaned pages with inbound links but no internal navigation. Fix those. They compound over time.

    2. Core Web Vitals trends. Use Google Search Console’s Core Web Vitals report to spot pages slipping from “Good” to “Needs Improvement.” Addressing layout shift or slow server response before it becomes a pattern prevents the slow bleed that looks like an algorithm penalty but isn’t.

    3. Competitor content gaps. Pick your five highest-traffic pages. Search their primary keywords and compare your structure, depth, and media to the top three results. If competitors added comparison tables, FAQs, or updated data in the last quarter, match or exceed it. This is offense, not defense.

    4. Publishing consistency. Track whether you’re hitting your content cadence. Two posts a week, one deep guide a month, whatever your baseline is—missing it for eight weeks will cost you more traffic than any algorithm update.

    None of these require real-time monitoring. None change during a core update rollout. All of them prevent the gradual entropy that kills organic traffic when you’re distracted by algorithm noise.

    When to actually react to an update

    Three scenarios justify immediate action after a core update:

    • You lose more than 40% of traffic to a specific content category, and it stays down for thirty days post-rollout. That’s a signal Google re-classified your topical authority. Audit those pages for expertise gaps, sourcing, or UX issues.
    • A manual action or security issue appears in Search Console during the same window. These aren’t algorithm updates—they’re penalties, and they require different fixes.
    • Your entire domain drops out of the index. Check Search Console for crawl errors, robots.txt mistakes, or noindex tags that shouldn’t be there. This is almost always operator error, not an algorithm decision.

    Outside those cases, wait thirty days, measure the delta, then decide whether to act. Most ranking shifts resolve themselves. The ones that don’t were caused by something you did—or didn’t do—not by Google changing its mind about quality overnight.

    One Two Three Send runs on the inertia model. No emergency pivots, no algorithm panic. Just monthly audits and consistent publishing. Subscribe for the next operator breakdown.

  • Stripe’s payment link expiration: when time limits boost conversions

    Stripe’s payment link expiration: when time limits boost conversions

    Stripe payment links let you sell anything without building a checkout page. You generate a URL, share it, and collect money. Simple. But buried in the link settings is an expiration option most operators never touch—and that’s a mistake.

    Payment link expiration does two things: it creates urgency for time-sensitive offers, and it prevents confusion when pricing or terms change. Set correctly, it can lift conversions. Set carelessly, it kills them.

    How payment link expiration works

    When you create a Stripe payment link, you can set an expiration date and time. After that point, the link returns a message telling the buyer it’s no longer available. The product, price, and subscription settings remain unchanged in your Stripe dashboard—only the link stops working.

    Stripe gives you three expiration options:

    • No expiration — the link works indefinitely
    • After a specific date and time — you pick an exact cutoff
    • After a certain number of uses — the link deactivates after X successful payments

    The third option is rarely useful unless you’re selling a fixed number of spots (a cohort course, a live workshop, a limited consulting package). The first two are where most operators make their choice—and where most get it wrong.

    When to set an expiration date

    Use expiration when the offer itself has a deadline. Launch pricing that ends Friday. Early-bird tickets for an event. A discount code you’re running for 72 hours. A beta program with a cap on participants.

    In these cases, expiration reinforces the urgency you’re already creating in your messaging. If your email says “price goes up Sunday at midnight,” the payment link should expire Sunday at 11:59 PM in your time zone. Otherwise, someone who bookmarks the link can return three weeks later and pay the old price—or worse, pay the old price for a product that no longer matches the description.

    Expiration also prevents billing confusion. If you raise your subscription price from $15 to $25 per month, every old payment link still floating around the internet will charge $15. Someone clicking a six-month-old tweet can subscribe at the wrong price, and you won’t know until you audit your transactions. Setting expiration on price-change announcements cleans this up automatically.

    When NOT to set expiration

    If the offer is evergreen, don’t add artificial urgency by setting a deadline you don’t intend to honor. Operators sometimes set a 30-day expiration “just in case,” then regenerate the same link with a new expiration date when it runs out. This creates busywork and breaks inbound links.

    Permanent products—courses, memberships, one-time purchases with stable pricing—should use non-expiring links. You can always deactivate a link manually in Stripe if you need to pull it, but starting with an arbitrary countdown adds friction without benefit.

    The exception: if you’re testing messaging or pricing and want to force yourself to revisit the offer in 90 days, expiration can act as a forcing function. Just be clear with yourself that it’s an internal deadline, not a customer-facing one.

    The non-obvious tactic: expiration as a segmentation signal

    Here’s a use case most operators miss. If you send the same payment link to two audiences—your email list and a Reddit post—you can’t tell which source drove the sale unless you use UTM parameters or create separate links.

    Creating separate links with different expiration dates lets you segment behavior without touching your analytics stack. Send a 48-hour expiring link to your email list and a 7-day expiring link to social. When you check Stripe’s payment link analytics, you’ll see which link performed and when it stopped converting.

    This isn’t a replacement for proper attribution tracking, but it works when you need a quick answer and don’t want to spin up a Zapier flow or tag every URL.

    A few operational notes

    Stripe doesn’t send you a notification when a payment link expires. If you set a date, add a calendar reminder to check performance and decide whether to extend, replace, or retire the offer.

    Expired links return a generic Stripe error page. You can’t customize the message, so if you want to redirect visitors somewhere else—an updated offer, a waitlist, an apology—you’ll need to remove the old link from circulation and replace it with a redirect or new page. Don’t rely on the Stripe expiration page to do marketing work for you.

    Finally, expiration applies to the link, not the product. If someone starts a checkout session before expiration and completes it five minutes after, Stripe still processes the payment. The cutoff happens at the moment someone clicks, not when they submit payment details.

    Set expiration when the offer has a real deadline. Skip it when the product is evergreen. And if you’re testing, use expiration to force a review date instead of letting old links quietly underperform.

    Want more tactical breakdowns like this? Subscribe to One Two Three Send—one article every day, no fluff, no filler.

  • WordPress database table prefixes: security theater or real protection?

    WordPress database table prefixes: security theater or real protection?

    Open any WordPress hardening guide and you’ll see the same advice: change your database table prefix from wp_ to something custom during installation. The logic sounds reasonable—if attackers don’t know your table names, they can’t exploit them. But after a decade of this being standard security advice, it’s worth asking whether it actually prevents anything meaningful.

    What the prefix actually does

    WordPress stores everything—posts, users, options, metadata—in a MySQL or MariaDB database. By default, tables are named wp_posts, wp_users, wp_options, and so on. The wp_ part is the prefix, defined in wp-config.php during installation.

    Changing it to something like xyz_ or j4k_ means your tables become xyz_posts, j4k_users, etc. The theory: SQL injection attacks that hardcode wp_users will fail because that table doesn’t exist in your database.

    In practice, modern SQL injection exploits don’t guess table names. They use SHOW TABLES or query the information_schema database to list everything, regardless of prefix. If an attacker has SQL injection access, your custom prefix buys you nothing—they’ll enumerate your schema in milliseconds.

    Where it breaks things

    Custom prefixes introduce friction in three places:

    Plugin compatibility. Most plugins handle prefixes correctly using WordPress’s $wpdb class, but older or poorly-maintained plugins sometimes hardcode wp_ in raw queries. You won’t know until something breaks in production.

    Manual database queries. If you ever need to run a direct SQL query—fixing a broken migration, bulk-updating post metadata, cleaning spam—you have to remember your custom prefix. Documentation and Stack Overflow answers assume wp_, so you’re translating every example.

    Migrations and cloning. Tools like WP Migrate DB, All-in-One WP Migration, and even hosting-panel cloners expect wp_ by default. Custom prefixes mean extra configuration steps, and if you’re moving between staging and production frequently, that’s friction you’ll feel every time.

    What actually hardens WordPress databases

    If your goal is to prevent database compromise, three things matter more than your table prefix:

    Separate database users with limited privileges. Your WordPress database user should only have SELECT, INSERT, UPDATE, and DELETE on its own database—not DROP, CREATE, or access to other databases. Most shared hosts set this up correctly, but if you’re on a VPS or managing your own MySQL instance, check SHOW GRANTS FOR 'your_db_user'@'localhost'; to confirm.

    Parameterized queries in custom code. If you’re writing your own plugin or theme functions that touch the database, use $wpdb->prepare() for every query with user input. This prevents SQL injection at the source, regardless of table names.

    Regular patching. Most WordPress database exploits come through outdated plugins, not core. If you’re running auto-updates for minor releases and reviewing plugin changelogs before major updates, you’re ahead of 80% of sites. A custom prefix won’t save you from a known vulnerability in a form plugin that’s six months behind.

    The verdict

    Changing your database prefix isn’t harmful—it just doesn’t deliver the security benefit it’s credited with. If you’re setting up a new site and the installer asks, there’s no reason not to customize it. But if you’re migrating an existing site or running a staging workflow where wp_ simplifies things, you’re not opening a meaningful vulnerability by leaving it default.

    Security checklists love to include it because it’s easy to explain and feels like hardening. But the threat model it addresses—automated scripts blindly guessing table names—hasn’t been relevant since 2010. Real attacks enumerate your schema or exploit application-layer vulnerabilities, and your prefix is irrelevant to both.

    Focus on database user permissions, parameterized queries, and keeping plugins updated. Those three will stop actual attacks. A custom prefix just makes your wp-config.php feel more secure.

    What’s your take? If you’ve seen a real-world case where a custom prefix stopped an attack, reply—I’d genuinely like to know. Otherwise, subscribe below for weekly deep-dives on the tools and tactics that actually move the needle for solo operators.