Author: onetwothreeadmin

  • Paid social ad budgets below $500/month: when to stop and redirect

    Paid social ad budgets below $500/month: when to stop and redirect

    Paid social ad budgets below $500/month: when to stop and redirect
    Photo by Quilia on Unsplash

    Most solo operators running paid social ads are spending just enough to lose money slowly. Not because the creative is bad or the targeting is wrong—but because platforms like Facebook, Instagram, and LinkedIn need minimum spend thresholds to exit learning phases and deliver stable results.

    If you’re spending under $500 per month on paid social, you’re likely feeding the algorithm without ever reaching the point where it can optimize effectively. Here’s when to pull the plug and put that budget somewhere else.

    Learning phases eat small budgets alive

    Facebook’s ad delivery system enters a “learning phase” every time you launch a new ad set. During this period—typically 50 conversions or about 7 days—the algorithm tests placements, audiences, and delivery patterns. Performance is unstable, cost per result is high, and you’re essentially paying for the platform to figure out what works.

    If your monthly budget is $300 and you’re optimizing for a conversion that costs $15, you’re generating 20 conversions per month. That’s not enough to exit learning on even one ad set, let alone test variants or scale what works. You reset the learning phase every time you tweak creative, adjust targeting, or pause for a few days.

    LinkedIn is worse. The platform recommends a minimum daily budget of $10 per campaign and suggests at least $5,000 in lifetime spend to gather meaningful data. At $400/month, you’re running a single campaign for weeks without enough signal to know if your targeting is directionally correct.

    Platform minimums and daily spend floors

    Beyond learning phases, each platform enforces daily or lifetime budget minimums that make small budgets impractical:

    • Facebook/Instagram: $1/day minimum per ad set, but realistic delivery starts around $5–10/day depending on audience size and objective.
    • LinkedIn: $10/day minimum per campaign. Sponsored Content and Message Ads often require $15–20/day to see consistent delivery.
    • Twitter/X: Minimum $5/day, but impressions taper off fast below $20/day.

    If you’re spending $300/month across two platforms, you’re looking at $5/day per platform—barely clearing minimums, never reaching statistical significance, and constantly restarting learning cycles when budget runs out mid-month.

    When organic or email wins instead

    Below $500/month, your budget is better spent on channels that don’t penalize you for small scale:

    Organic social scheduling and repurposing. A $15/month tool like Publer or Buffer lets you schedule posts across networks, repurpose content into multiple formats, and maintain consistent presence without paying for each impression. You lose paid reach, but you also stop hemorrhaging budget into learning phases that never resolve.

    Email list growth via lead magnets. Put $200/month into a freelance designer for lead magnet PDFs, landing page tweaks, or content upgrades. Pair that with organic promotion and you’re building an owned channel that doesn’t reset when you pause spend. Platforms like MailerLite or Brevo let you grow to 1,000+ subscribers for free or under $15/month, and every subscriber you add compounds over time.

    Low-cost traffic tests via solo ads or newsletter sponsorships. If you need paid traffic and have a tight budget, solo ads in your niche (typically $0.30–0.70 per click) or small newsletter sponsorships ($50–150 per placement) give you fixed-cost exposure without algorithmic gatekeeping. You know exactly what you’re paying per visitor, and there’s no learning phase tax.

    The breakpoint: $500/month, single platform, single objective

    If you’re committed to paid social, here’s the minimum viable threshold:

    • $500+/month on one platform (not split across three)
    • One conversion objective (not awareness + traffic + conversions in parallel)
    • At least 50 conversions per month to exit and re-enter learning without burning budget

    Below that, you’re paying for noise. The algorithm can’t learn, you can’t test, and your cost per result stays artificially high because the platform never stabilizes delivery.

    Run the math on your last 90 days. If your monthly spend averages under $500 and you’re not seeing consistent cost-per-result improvements month over month, pull the budget. Redirect it to owned channels, test organic tactics for 60 days, and return to paid social only when you have the budget to clear learning phases without restarting every week.

    Reply with your current monthly paid social spend and conversion count—I’ll tell you if you’re above or below the threshold where the algorithm can actually work.

    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.

  • Google Analytics 4 event parameters: which ones actually fire

    Google Analytics 4 event parameters: which ones actually fire

    Google Analytics 4 event parameters: which ones actually fire
    Photo: Ajiro Shinpei via Wikimedia Commons (CC BY-SA 4.0)

    Google Analytics 4 lets you attach up to 25 custom parameters to each event. That sounds generous until you realize that half of them might never show up in your reports—and the platform won’t tell you which ones got dropped until you go looking.

    If you’re running a content business and relying on GA4 to track subscriber behavior, link clicks, or content engagement, this matters more than you think. Parameters that don’t fire mean attribution you can’t trace and conversion paths you can’t rebuild.

    Why parameters get dropped

    GA4 has three silent filters that strip parameters before they hit your data stream:

    Character limits. Parameter names max out at 40 characters. Values cap at 100 characters. Cross either threshold and the entire parameter vanishes. No error message, no warning in the real-time view—it just doesn’t arrive.

    Reserved parameter names. GA4 reserves roughly 50 parameter names for its own use. If you accidentally name a custom parameter something like page_location, session_id, or user_id, your value gets overwritten by GA4’s automatic collection. The list isn’t published in one place; you have to cross-reference the automatically collected events documentation to catch conflicts.

    Data type mismatches. GA4 infers parameter types from the first value it sees. If your first event sends article_id as a string and the second sends it as an integer, one of them won’t register as a custom dimension. The platform doesn’t reconcile types retroactively, so you’re left with partial data and no alert.

    How to audit what’s actually firing

    Most operators check the real-time report and assume everything’s working. That view only shows the last 30 minutes and doesn’t surface parameter-level failures.

    The better approach: open the DebugView panel in GA4. It requires enabling debug mode in your Google Tag Manager container or adding ?debug_mode=true to your site URL if you’re using gtag.js directly. DebugView shows every event and parameter as it arrives, including which ones GA4 accepted and which ones it ignored.

    Run a test session where you trigger each custom event you care about—newsletter signups, affiliate link clicks, content unlocks, purchase completions. Watch the parameter list for each event. If a parameter you sent isn’t listed, it got dropped.

    Once you know which parameters aren’t firing, check their names against the reserved list, confirm their character counts, and verify that your tag configuration isn’t sending mixed data types.

    When to use event-scoped vs. user-scoped parameters

    GA4 parameters come in two flavors: event-scoped and user-scoped. Event-scoped parameters attach to individual actions—link_url, video_title, form_id. User-scoped parameters attach to the visitor across sessions—subscription_tier, signup_source, customer_ltv.

    The distinction matters because GA4 limits you to 50 custom event-scoped dimensions and 25 custom user-scoped dimensions per property. If you’re tracking a membership site or a newsletter with multiple tiers, user-scoped parameters let you segment reports without burning through event slots.

    But user-scoped parameters don’t populate immediately. GA4 only attaches them after the first event in a session fires. If you need to filter reports by subscription_tier and a visitor’s first event is a page view, the tier parameter won’t be available until the second event. That creates reporting gaps if you’re trying to attribute conversions by tier.

    The workaround: send a silent user_properties_set event on page load with all your user-scoped parameters, then fire your standard events afterward. It’s an extra event in your tag setup, but it ensures parameters are available from the first interaction.

    One non-obvious fix

    If you’re sending parameters through Google Tag Manager and they’re still not showing up in GA4, check your tag’s Fields to Set configuration. GA4 tags in GTM have two places to define parameters: the “Event Parameters” section and the “Fields to Set” section. Event parameters go to GA4. Fields to Set go to the Google tag itself and don’t always pass through to GA4 reports.

    Most operators fill out Fields to Set by mistake and wonder why their custom dimensions stay empty. Move everything to Event Parameters unless you’re explicitly configuring a Google Ads conversion tag.

    If you’re running attribution reporting across multiple tools—GA4, your newsletter platform, and a course LMS—make sure your parameter names stay consistent. GA4 doesn’t auto-map source to utm_source or email to user_email. Pick a naming convention and enforce it across every tag, webhook, and API call.

    Want more breakdowns like this? Reply with the analytics setup you’re trying to debug—I’ll cover it in a future issue.

  • WordPress database character set conversion: when emoji breaks old posts

    WordPress database character set conversion: when emoji breaks old posts

    WordPress database character set conversion: when emoji breaks old posts
    Photo by Markus Winkler on Unsplash

    Most WordPress sites installed before 2015 run on a latin1 character set. That was fine until emoji, non-Latin scripts, and special characters became standard in content. The fix is converting to utf8mb4, the MySQL character set that supports the full Unicode range. But the migration isn’t one-click safe, and operators who skip the collation audit often find mangled text in old posts weeks later.

    Why utf8mb4 matters now

    WordPress has shipped with utf8mb4 as the default character set since version 4.2 in 2015. If your database predates that, or if your host used an older MySQL template during setup, you’re likely still on latin1 or the older three-byte utf8 encoding.

    The three-byte utf8 can’t store emoji, many Asian characters, or mathematical symbols. If a reader pastes an emoji into a comment form or you copy-paste content from a doc that includes a smart quote outside the latin1 range, MySQL either rejects the insert or converts the character to a question mark.

    utf8mb4 uses four bytes per character and covers the entire Unicode standard. That means emoji, CJK ideographs, and symbols render correctly and survive round-trip edits in the WordPress editor.

    The collation mismatch that breaks content

    Character set defines which characters MySQL can store. Collation defines how MySQL compares and sorts them. When you convert a database from latin1 to utf8mb4, you also change the collation—typically from latin1_swedish_ci to utf8mb4_unicode_ci or utf8mb4_general_ci.

    If your existing content contains extended ASCII characters—curly quotes, em dashes, accented letters—and those were stored as latin1 bytes, a straight conversion can misinterpret them. MySQL reads the old byte sequence as if it were already utf8mb4, producing garbled output: “don’t” instead of “don’t.”

    This happens because the byte 0x92 in latin1 (right single quotation mark) has a different meaning in utf8mb4. The conversion tool changes the schema but doesn’t transcode the data unless explicitly told to.

    How to convert without breaking old posts

    The WordPress database charset converter in wp-admin → Tools → Database (added in WP 4.2) handles most cases, but it only appears if WordPress detects a charset mismatch. If the tool doesn’t show, your database and wp-config.php already match, or your host disabled the feature.

    For manual conversion, use the WP-CLI command:
    wp db convert-charset utf8mb4

    This command checks each table, converts the schema, and attempts to recode existing data. But it can’t fix data that was already double-encoded or incorrectly inserted under the old charset.

    Before you run any conversion:

    • Export a full database backup via phpMyAdmin or your host’s backup tool.
    • Check your MySQL version. utf8mb4 requires MySQL 5.5.3 or later; most hosts are on 8.0+ now, but confirm.
    • Run a test query to identify problem rows:
      SELECT post_title, post_content FROM wp_posts WHERE post_content LIKE '%â€%' LIMIT 20;
      If you see garbled characters, your data is already mis-encoded and needs a two-step fix.

    If you find corrupted rows, you’ll need to convert from the actual source encoding. Many databases labeled latin1 were silently storing utf8 bytes. In that case, convert from utf8 to utf8mb4, not latin1 to utf8mb4. Tools like mysqldump with the --default-character-set flag let you specify the real source encoding.

    What breaks after conversion

    Index length limits are the most common post-conversion issue. utf8mb4 uses four bytes per character, so a VARCHAR(255) index that worked under latin1 (255 bytes) now requires 1,020 bytes (255 × 4). MySQL’s default InnoDB index prefix limit is 767 bytes on older configurations.

    If you hit this during conversion, you’ll see:
    ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

    The fix: enable innodb_large_prefix and set innodb_file_format=Barracuda in your MySQL config, or shorten the indexed column. Most modern managed WordPress hosts (including BigScoots) run MySQL 5.7+ with large prefixes enabled by default, but check if you manage your own VPS.

    Plugins that write custom tables may also need updates. If a plugin creates a table after your conversion, it might default to the old charset unless the plugin explicitly sets utf8mb4 in its schema. Check any membership, LMS, or e-commerce plugins with custom tables.

    When to skip the conversion

    If your site is English-only, has no user-generated content, and you never use emoji or special characters, the conversion won’t break anything—but it also won’t deliver visible value. The character set becomes important when:

    • You accept comments, forum posts, or form submissions from readers
    • You publish multilingual content or quote non-English sources
    • You paste from Google Docs, Notion, or other tools that insert smart quotes and em dashes
    • You use emoji in headlines, CTAs, or social-share snippets

    For solo operators running a single-language blog with no UGC, the status quo works. But if you plan to scale, open comments, or accept guest posts, convert now before you have 500 posts to audit.

    One last thing: After conversion, update your wp-config.php to match. Set:
    define('DB_CHARSET', 'utf8mb4');
    define('DB_COLLATE', 'utf8mb4_unicode_ci');

    Without that, WordPress will keep writing new content in the old charset, and you’ll be back where you started.

    Hit a wall with WordPress hosting performance or database issues? Subscribe to One Two Three Send for operator-focused breakdowns every week—no fluff, just the technical details that matter.

  • Transactional email rate limits: when your invoices queue for hours

    Transactional email rate limits: when your invoices queue for hours

    Transactional email rate limits: when your invoices queue for hours
    Photo by CHUTTERSNAP on Unsplash

    Transactional email services advertise instant delivery, but nearly every provider enforces rate limits you don’t see until you hit them. When your WordPress site queues 200 password reset emails during a product launch, or your payment processor triggers 150 invoice emails in ten minutes, those messages don’t all send immediately—they stack in a queue, and some users wait 20 minutes for a confirmation that should arrive in seconds.

    Rate limits exist to protect sender reputation and prevent abuse, but the default caps are lower than most operators expect. If you’re running a content business with any spike traffic—course enrollments, flash sales, member signups—you need to know where your transactional provider throttles, and what happens when you exceed it.

    Where rate limits hide

    Postmark caps free-tier accounts at 10 emails per second, which sounds generous until you calculate it: 600 emails per minute, 36,000 per hour. If you’re sending order confirmations during a product launch and processing 400 transactions in fifteen minutes, you’re fine. But if your WordPress install also queues comment notifications, password resets, and admin alerts through the same API key, those all count toward the same limit.

    Mailgun’s free tier allows 100 emails per hour—3.6 seconds per message if you max it out. That’s sufficient for a solo blog with light transactional volume, but it breaks the moment you add a membership plugin that emails welcome sequences or sends bulk password resets after a database migration.

    SendGrid enforces a default limit of 600 emails per day on free accounts, and 100 per hour on some paid tiers unless you request an increase. The daily cap resets at midnight UTC, not your local timezone, so if you launch a paid product at 11 PM Eastern and process 200 sales before 12 AM UTC, you’ll hit the limit and queue the rest until the next day.

    What queuing actually does

    When you exceed a rate limit, most transactional providers queue your emails and release them gradually. Postmark holds messages in a send queue and drips them out at the max rate; users see a 5–10 minute delay for emails sent during a spike. Mailgun does the same, but queues can extend to 30 minutes if you’re on a free tier during high-traffic periods across their network.

    The problem isn’t the queue itself—it’s that your application doesn’t know about it. WordPress confirmation plugins assume emails send instantly, so users refresh their inbox, check spam, then contact support. Your transactional dashboard shows all messages as “accepted,” but the timestamp reflects when the email entered the queue, not when it actually delivered.

    Some providers reject emails that exceed the rate limit outright. SendGrid returns a 429 error, and unless your application retries the API call, the email never sends. Most WordPress SMTP plugins don’t retry by default; they log a failure and move on. You won’t know an invoice email failed unless you manually check logs or a customer complains.

    How to audit your current limit

    Log into your transactional email dashboard and look for “account limits” or “sending rates” under settings or billing. Postmark lists it on the account overview page. Mailgun buries it under “Account Settings → Sending” and labels it “hourly sending limit.” SendGrid shows daily and hourly caps under “Settings → Account Details.”

    If you can’t find the limit documented, send a test batch. Queue 150 emails through your transactional API in under one minute using a script or a WordPress plugin like WP Mail SMTP’s test tool, then watch your dashboard. Check the “sent” timestamps—if they spread across 10–15 minutes, you hit a throttle.

    Compare your rate limit to your actual transactional volume. Pull your email logs for the last 30 days and calculate your peak hourly send rate. If your highest spike was 220 emails in one hour and your provider caps you at 100, you’re queuing messages during every launch or enrollment period.

    When to upgrade vs. switch

    Most transactional providers let you request a rate limit increase on paid plans. Postmark raises limits to 70 emails per second (252,000 per hour) on their $15/month tier. Mailgun increases hourly caps to 10,000 on paid accounts. SendGrid adjusts daily limits to 40,000+ once you’re on a $20/month plan and submit a support ticket.

    If you’re already on a paid plan and still hitting limits during normal operations, switch providers. A $20/month plan that queues invoices for 20 minutes during a product launch costs more in lost sales and support time than a $50/month plan that delivers instantly.

    Postmark is the cleanest option for operators who need reliable transactional delivery without rate-limit surprises. The interface is operator-friendly, the docs explain limits up front, and you can monitor queue depth in real time.

    Check your transactional provider’s dashboard today. Find your rate limit, compare it to your peak send volume, and decide whether you need an upgrade before your next product launch. If you’re not sure where to start, reply with your monthly transactional volume—I’ll tell you which tier makes sense.

  • Productivity workflows fail when tools don’t expose failure logs

    Productivity workflows fail when tools don’t expose failure logs

    Productivity workflows fail when tools don't expose failure logs
    Photo by Kelly Sikkema on Unsplash

    Automation promises to save time. Connect two tools, map a few fields, and walk away while your workflow runs itself. Except most of the time, it doesn’t—and you won’t know until something downstream breaks.

    The problem isn’t the automation itself. It’s that most productivity and workflow tools hide failure logs, or worse, don’t generate them at all. When a Zap doesn’t fire, a webhook times out, or an API call returns a 429 rate-limit error, you’re left guessing what happened and when.

    This isn’t a minor inconvenience. For solo operators running content-driven businesses, silent failures mean missed subscriber imports, unsent confirmation emails, broken payment flows, and hours spent reconstructing what should have been logged automatically.

    What actually breaks—and why you don’t see it

    Workflow automation tools connect APIs. When one service updates its endpoint, changes authentication requirements, or throttles your request rate, the connection breaks. The tool might retry once or twice, then stop. But unless you’re checking a dashboard daily, you won’t notice.

    Here’s what typically fails without clear logs:

    • Webhook deliveries. Your payment processor sends a webhook to your automation tool. The tool’s endpoint is down for 90 seconds. The processor retries twice, then gives up. You never see the payment event.
    • Rate-limited API calls. You hit your monthly quota on a CRM sync. New contacts stop importing. The workflow shows “active,” but nothing moves.
    • Field mapping errors. A form adds a new required field. Your automation doesn’t map it. Submissions fail silently because the downstream tool rejects incomplete data.
    • Conditional logic that misfires. A filter checks for a specific tag. The tag format changes (lowercase to Title Case). The condition never matches again, and the workflow stops routing.

    Most tools surface these as generic “error” badges or email you a vague summary. Few show you the actual API response, the timestamp, or the payload that failed.

    What to look for in a workflow tool’s failure visibility

    Before you commit to a productivity automation platform, test how it handles failure. Create a workflow that you know will break—send data to a fake endpoint, or map a required field incorrectly—and see what the tool tells you.

    Good failure logs include:

    • Exact timestamp of the failure, not just the day.
    • Full API response or error code from the downstream service.
    • Payload preview—what data was sent, so you can see if the mapping is wrong.
    • Retry history—how many times the tool attempted to resend, and what happened each time.
    • Alerting options—email, Slack, or webhook notification when a workflow fails more than once in an hour.

    Zapier’s Task History shows attempt timestamps and error codes, but you need to dig into each task individually. Make (formerly Integromat) offers better visibility: you can see the full JSON response and re-run failed scenarios from the log. n8n, if you’re self-hosting, gives you raw execution data and lets you export logs as JSON.

    If a tool doesn’t show you why something failed—just that it did—you’re paying for a black box.

    How to build failure-aware workflows

    Even with good logging, you need to design workflows that assume failure. That means adding fallback paths, monitoring steps, and external checks.

    Add a logging step to critical workflows. Send a copy of each payload to a Google Sheet or a simple database. If the main workflow fails, you have a timestamped record of what should have happened. This costs an extra task per run, but it’s worth it for high-stakes automations like payment processing or subscriber onboarding.

    Set up a daily reconciliation check. Compare your source data (e.g., new Stripe customers) to your destination (e.g., CRM contacts added). If the counts don’t match, something broke overnight. A simple script or low-code tool can handle this—you’re just counting rows and sending yourself a summary email.

    Use webhooks with confirmation endpoints. When possible, configure your workflow to send a confirmation payload back to the originating service. If the confirmation doesn’t arrive, the source knows to flag it. Not every tool supports this, but payment processors and CRMs often do.

    Avoid chaining too many conditional branches in one workflow. Each “if/then” step is another place where logic can misfire silently. Split complex workflows into smaller, single-purpose automations that are easier to debug when something breaks.

    When to move off a tool that hides failures

    If you’re spending more than an hour a week troubleshooting workflows because you can’t see what failed, it’s time to switch. The cost of missed automations—lost leads, unsent emails, broken payment flows—is higher than the friction of migrating to a platform with real logging.

    For solo operators, visibility isn’t a luxury. It’s the difference between automation that saves time and automation that creates silent technical debt. Choose tools that show you what broke, when it broke, and why—or build your own failure logs into every workflow that matters.

    What workflow tools do you use, and how do they handle failure logging? Reply with your setup—we’re collecting operator feedback for a deeper comparison piece.

  • AI summarization tools chop context you still need—here’s what gets lost

    AI summarization tools chop context you still need—here’s what gets lost

    AI summarization tools chop context you still need—here's what gets lost
    Photo: DataBase Center for Life Science (DBCLS) via Wikimedia Commons (CC BY 4.0)

    AI summarization tools promise to collapse 3,000-word articles into 300-word digests. They work—but they work by making editorial choices you didn’t authorize. For solo operators running content-driven businesses, those choices often discard the exact context that makes source material useful.

    If you’re summarizing competitor analysis, customer research, or technical documentation to brief yourself or your team, understanding what AI summarizers routinely drop will save you from acting on incomplete information.

    What gets cut first: hedges, conditions, and attribution

    Most summarization models prioritize declarative statements and strip conditional language. A source sentence like “In markets where CAC exceeds $80, paid social often underperforms organic by 15–20% according to three operators we surveyed” becomes “Paid social underperforms organic.”

    Three critical pieces disappear: the $80 CAC threshold, the 15–20% range, and the sample size. You’re left with a claim that sounds absolute but was contextual. If your CAC is $40, the original finding may not apply—but the summary won’t tell you that.

    Source attribution drops even faster. Summarizers treat citations and hedges as filler. “According to Databox’s Q2 report” becomes invisible. If you later want to verify a claim or check the methodology, you’ll have to re-read the original—which defeats half the purpose of summarizing in the first place.

    Nuance flattens into binary claims

    Summarization models favor simplicity. A paragraph explaining that email open rates vary by send time, audience segment, subject line length, and day of week might reduce to “Send time affects open rates.” Technically true. Operationally useless.

    This flattening is most dangerous when summarizing case studies or operator interviews. A founder saying “We tried affiliate marketing for six months, saw no traction, then switched our link placement strategy and 3x’d revenue in 90 days” often summarizes to “Affiliate marketing worked after six months.” The strategy shift—the actual insight—vanishes.

    If you’re summarizing content to extract takeaways for your own projects, you need the conditions and the pivots. The summary gives you the outcome without the mechanism.

    Edge cases and exceptions disappear

    AI summarizers optimize for the majority case. Exceptions, outliers, and “but if you’re in X situation, do Y instead” clauses get trimmed as noise.

    A guide explaining that WordPress caching plugins speed up most sites but break membership paywalls and logged-in user experiences will summarize to “Caching plugins speed up WordPress.” If you run a membership site, you just got advice that will break your business.

    The same happens with tool recommendations. An article comparing three email platforms—two general-purpose and one for e-commerce operators with Shopify integrations—might summarize without preserving the Shopify caveat. You’ll see “Platform A is cheaper” without the asterisk that it only works if you don’t need e-commerce features.

    When to summarize and when to skim yourself

    AI summarization works well for news aggregation, surface-level topic scanning, and filtering content you’ll never revisit. If you’re reading ten competitor blogs to check for overlapping topics, a summarizer saves time.

    Skip the summarizer when you’re extracting decision-critical details: pricing research, technical setup guides, operator case studies with metrics, or any content where the “how” matters as much as the “what.” For those, skim the original yourself or use the summarizer as a first pass, then read the sections it flags as important.

    If you do summarize, keep the original link in your notes. Tools like Claude let you upload documents or paste long text for summarization—useful when you control the prompt and can ask it to preserve conditions, citations, and ranges. Default summarizers in browser extensions and read-it-later apps rarely let you tune their behavior.

    The time you save summarizing often gets spent re-reading later when you realize a key detail is missing. For high-stakes decisions, read the source. For everything else, summarize—but know what you’re trading away.

    Want more tools and workflows for solo operators? Subscribe to One Two Three Send for weekly breakdowns of how online-business software actually works.

    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 automation filters stack in reverse order—here’s how

    ConvertKit’s visual automation builder looks intuitive—drag a few boxes, connect some arrows, add a filter or two, and you’re done. But there’s a non-obvious quirk in how the platform evaluates filter conditions that trips up even experienced operators: filters stack in reverse order.

    If you’ve ever set up an automation rule with multiple conditions and watched subscribers either flood through when they shouldn’t or fail to trigger when they should, this is the mechanic that explains it.

    How filter stacking actually works

    ConvertKit evaluates conditions from the bottom of your filter list upward, not top-to-bottom like you read. If you add three conditions—say, “has tag A,” “does not have tag B,” and “signed up more than 7 days ago”—the platform checks the bottom condition first, then works its way up.

    This matters when you’re using AND/OR logic. A rule that reads clearly in your head when stacked top-to-bottom can behave completely differently when evaluated in reverse.

    Here’s a concrete example: you want to send a product launch email only to subscribers who opened your last broadcast and don’t already own the product. You add two filters:

    • Top filter: Opened broadcast “Pre-launch Teaser”
    • Bottom filter: Does not have tag “Customer”

    ConvertKit checks the “Customer” tag first. If that passes, it checks the open. But if you’re using OR logic somewhere in the stack, the sequence can fire for anyone without the tag—even if they never opened the email.

    The fix is to reverse your mental model: build filters from the bottom up, with your most restrictive condition at the top of the list.

    When this breaks automations silently

    The stacking order doesn’t throw an error. Your automation just underperforms, and you won’t know why unless you manually trace a few subscriber paths.

    Common failure modes:

    • Welcome sequences fire for existing customers. You added a “new subscriber” filter at the top, but an OR condition lower in the stack lets tagged users through.
    • Upsell emails go to people who already bought. You filtered for “does not have tag X,” but a date-based condition evaluated first and passed everyone through.
    • Re-engagement campaigns trigger immediately. You set “inactive for 30 days” at the top, but ConvertKit checked a different condition first and started the sequence early.

    These bugs don’t show up in the builder UI. The visual flow looks correct. But the logic runs backward.

    How to audit your existing automations

    Open each automation rule and list your filters on paper, bottom to top. Then walk through the logic as ConvertKit would execute it. Ask:

    • Does the bottom condition pass the right people?
    • If yes, does the second-to-bottom refine or expand that group?
    • Are any OR statements letting in subscribers you didn’t intend?

    If you’re using more than three filters, consider splitting the automation into two rules. ConvertKit’s AND/OR toggle applies to the entire stack, so complex logic gets hard to reason about fast.

    For high-stakes sequences—product launches, course enrollments, refund follow-ups—test with a single subscriber first. Tag yourself, trigger the automation manually, and confirm you land in the right sequence at the right time.

    One non-obvious workaround

    If you need granular control and can’t get the filter stack to behave, move your logic into tags instead of filters. Create a dedicated tag that represents the exact subset of subscribers you want to target, then use a separate automation to apply that tag based on behavior.

    For example, instead of filtering for “opened email A AND does not have tag B AND signed up more than 7 days ago,” create a tag called “Launch-Eligible” and use one automation to apply it when all three conditions are true. Then trigger your launch sequence with a single filter: “has tag Launch-Eligible.”

    It’s an extra step, but it eliminates ambiguity. You’re offloading the logic from ConvertKit’s filter stack into a tagging rule you control, and the sequence itself becomes a simple on/off switch.

    This approach also makes debugging faster. If someone didn’t receive the email, you check whether they have the tag. If they don’t, you trace back through the tagging automation to see which condition failed.

    ConvertKit’s automation builder is powerful, but the reversed filter evaluation is a sharp edge. Once you know how it works, you can design around it. Until then, it’s easy to ship a sequence that looks right but runs wrong.

    Got a ConvertKit automation that’s misbehaving? Reply with the filter setup—I’ll walk through how the platform is likely evaluating it.

  • Attribution windows shorter than your sales cycle break conversion tracking

    Attribution windows shorter than your sales cycle break conversion tracking

    Attribution windows shorter than your sales cycle break conversion tracking
    Photo: Haymillefolium via Wikimedia Commons (GPLv3)

    Attribution windows define how long a platform remembers where a visitor came from before they convert. Google Analytics defaults to 90 days for most models. Facebook Ads dropped to 7-day click, 1-day view in 2021 and hasn’t budged. Most affiliate networks sit at 30 days. And if you’re running a content business with a considered purchase—a course, a yearly subscription, a high-ticket consulting package—there’s a good chance your actual sales cycle is longer than every attribution window you’re using.

    When that happens, you stop seeing where revenue actually comes from. You start making decisions on incomplete data. And you end up over-investing in channels that look good on a short clock while starving the ones that drive long-term growth.

    What attribution windows actually control

    An attribution window is the trailing period during which a platform will connect a conversion back to a traffic source. If someone clicks your Facebook ad, thinks about it for eight days, then subscribes, Facebook won’t count that conversion—because its click attribution window is seven days. The sale still happened. You just can’t see what caused it.

    This matters less for impulse purchases. If you’re selling a $19 ebook and most people buy within an hour of landing on the page, a 7-day window is fine. But if you’re selling a $500 course, and your average buyer reads four articles, downloads a lead magnet, gets three emails, and converts 18 days later, that 7-day window is functionally useless.

    The result: you see “direct” or “none” as your top conversion source, because the original referral aged out. You assume organic works. You double down on SEO. Maybe it does work—but you’re guessing, because the system threw away the breadcrumb trail halfway through.

    How to audit your funnel against your windows

    Pull your actual time-to-conversion data. In Google Analytics 4, go to Reports > Engagement > Conversions, then add a secondary dimension for “Days to conversion.” Export it. Calculate the median and 75th percentile. That’s your real sales cycle.

    Now compare it to every attribution window you rely on:

    • Google Analytics 4: 90 days for data-driven attribution by default, but many operators still use last-click models with shorter windows
    • Facebook/Instagram Ads: 7-day click, 1-day view
    • LinkedIn Ads: 90-day click by default, but viewthrough is much shorter
    • Affiliate platforms: typically 30–90 days depending on the network
    • Email platforms: effectively infinite if you’re tracking via UTM, but many operators rely on platform-native conversion tracking that resets on re-engagement

    If your 75th percentile time-to-conversion is longer than your shortest attribution window, you’re losing signal. And because short-window platforms tend to be the ones with the biggest budgets (paid social), you’re probably misallocating spend.

    Fixing it without ripping out your stack

    You can’t change Facebook’s attribution window. But you can layer your own. The simplest fix: use a dedicated attribution tool that tracks first touch, last touch, and everything in between with a custom window. Tools like Hyros, Attributer, and Wicked Reports let you set windows as long as you want and tie them to actual revenue, not just platform-reported conversions.

    If you’re not ready to pay for another SaaS layer, extend your own tracking. Use UTM parameters religiously, store them in a cookie or your CRM with a long expiration, and tie conversions back manually in a spreadsheet or your database. It’s not elegant, but it works. Set your cookie to expire at 180 days if your real sales cycle is 60–90. Give yourself margin.

    Another option: shift budget toward channels with longer native windows. Google Search and SEO have effectively infinite attribution if someone converts in-session. Email works the same way. Organic social is harder to track, but if you’re using link shorteners with your own UTMs and a long cookie, you control the window.

    The worst move is to keep running paid social, see “direct” conversions climb, assume brand is working, and never connect the dots. That’s not brand lift. That’s attribution collapse.

    When to care and when to ignore it

    If your median time-to-conversion is under 7 days and you’re running straightforward offers, this doesn’t matter. Optimize in-platform, trust the numbers, move on.

    But if you’re selling anything that requires consideration—courses, memberships, coaching, SaaS annual plans—or if you’re seeing a growing share of “direct” traffic that converts better than named sources, your attribution windows are probably lying to you. Fix the measurement before you fix the marketing.

    Want more breakdowns like this? Subscribe to One Two Three Send and get one operator-focused deep-dive in your inbox every week.

  • Most operators track too many social platforms—here’s the cutoff

    Most operators track too many social platforms—here’s the cutoff

    Most operators track too many social platforms—here's the cutoff
    Photo by Joel Rivera-Camacho on Unsplash

    The default advice for growing an online business is to be everywhere: Twitter, LinkedIn, Instagram, Threads, Bluesky, TikTok. Post daily. Engage relentlessly. Cross-promote.

    In practice, most solo operators and small teams spread themselves so thin across platforms that they can’t tell which channels actually deliver. They post sporadically, track inconsistently, and burn hours context-switching between apps with no clear return.

    The contrarian take: you should be active on two social platforms, maybe three. Any more than that and you’re optimizing for presence instead of results.

    The cost of multi-platform distribution

    Every platform you maintain has a real time cost. You need to:

    • Adapt content format and tone for each audience
    • Monitor replies, DMs, and mentions
    • Track referral traffic and conversions separately
    • Stay current on algorithm changes and feature rollouts
    • Manage posting schedules that don’t overlap poorly

    A single post on LinkedIn might take 10 minutes to write and format. The same core idea reshaped for Twitter threads, Instagram carousels, and TikTok scripts can easily consume two hours. If you’re posting three times a week across four platforms, that’s 24 hours a month just on content creation—before engagement, before analytics.

    And unlike email, where you own the relationship, social platforms can change reach overnight. You’re renting attention, and the lease terms shift without notice.

    How to pick your two platforms

    Start with where your revenue actually comes from. Open your analytics tool—Google Analytics, Fathom, Plausible, whatever you use—and filter traffic sources for the last 90 days. Look at:

    • Sessions from each social platform
    • Conversion rate (email signups, purchases, course enrollments)
    • Time on site—are visitors sticking around or bouncing?

    Most operators discover that 80% of their social-driven conversions come from one or two platforms. Often it’s LinkedIn for B2B, Twitter for tech audiences, or Instagram for visual products. The other four platforms deliver sporadic traffic that rarely converts.

    If you don’t have 90 days of data, run a 30-day test. Post consistently on your current platforms and track everything with UTM parameters (?utm_source=twitter&utm_medium=social&utm_campaign=july). The platform that delivers qualified traffic wins. The rest are candidates for shutdown.

    What about discovery and reach?

    The common objection: “But I need to be everywhere for discovery. What if my audience is on Platform X?”

    Discovery is overrated for most online businesses. If you’re running a newsletter, a course, or a productized service, your growth comes from being consistently valuable in one place, not sporadically present in six. A weekly LinkedIn post that gets 40 comments and five email signups beats a daily TikTok habit that generates 2,000 views and zero conversions.

    The operators who grow fastest pick one primary platform and one backup. Primary gets 70% of effort: original content, deep engagement, network building. Backup gets 30%: reposts, highlights, low-effort syndication. Everything else gets abandoned or automated into oblivion.

    Tools like Publer can help with cross-posting if you want a minimal presence elsewhere, but even then, you’re better off focusing energy than fragmenting it.

    When to add a third platform

    Add a third platform only when:

    • Your two primary channels consistently deliver 20+ conversions per month each
    • You have a specific, proven audience segment that only exists on the new platform
    • You can hire or delegate content creation so your personal time doesn’t increase

    Until then, double down on what works. A single platform with deep engagement and clear attribution beats shallow presence across the entire social web.

    If this resonated, subscribe to One Two Three Send—we cover the infrastructure and tooling decisions that actually move the needle for solo operators and small teams.

    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 image optimization plugins: lazy load vs. CDN rewrite logic

    WordPress image optimization plugins: lazy load vs. CDN rewrite logic

    WordPress image optimization plugins: lazy load vs. CDN rewrite logic
    Photo by sarah b on Unsplash

    Most WordPress sites run at least one image optimization plugin. Many run two or three at once: a lazy load plugin, a CDN rewrite plugin, and maybe an image compression tool. The problem is that these plugins don’t coordinate—they stack transformations on top of each other, and sometimes those transformations conflict.

    The result isn’t always visible. Your images might load fine on desktop, then break on mobile. Or they load, but the CDN never serves them. Or lazy load fires twice, delaying the render by an extra second.

    Here’s how lazy load and CDN rewrite logic actually work, where they collide, and how to configure them so they don’t cancel each other out.

    Lazy load rewrites your image tags before the page renders

    Lazy load plugins work by intercepting the HTML output buffer just before WordPress sends the page to the browser. They scan for <img> tags, rewrite the src attribute to a placeholder (often a transparent 1×1 GIF or inline SVG), and move the real image URL into a data-src attribute. A JavaScript library watches the viewport and swaps data-src back into src when the image is about to scroll into view.

    That rewrite happens after WordPress generates the HTML, but before it gets sent to the browser. Most lazy load plugins hook into the the_content filter or the final output buffer, which means they’re one of the last things to touch the page.

    The timing matters, because CDN rewrite plugins also hook into the output buffer—and they’re looking for image URLs to rewrite.

    CDN rewrite plugins replace your domain with the CDN endpoint

    A CDN rewrite plugin scans the same HTML output buffer and looks for any URL pointing to your domain. When it finds an image URL like https://yourdomain.com/wp-content/uploads/image.jpg, it replaces the domain with your CDN endpoint: https://cdn.yourdomain.com/wp-content/uploads/image.jpg or https://abc123.cloudfront.net/image.jpg.

    That rewrite is simple: it’s a string replacement. The plugin doesn’t parse the HTML into a DOM tree—it just searches for your domain and swaps it out.

    But if the lazy load plugin has already moved the real image URL into data-src, the CDN plugin might not find it. Some CDN plugins only rewrite src attributes. Others rewrite both src and data-src, but the order matters.

    When the wrong plugin runs first, images don’t serve from the CDN

    If the lazy load plugin runs before the CDN rewrite plugin, the CDN plugin sees src="data:image/svg+xml…" and data-src="https://yourdomain.com/image.jpg". If it’s only configured to rewrite src, it skips the image. The image loads from your origin server, not the CDN.

    You won’t see an error. The image still loads. But you’re paying for CDN bandwidth and getting none of the latency benefit.

    If the CDN plugin runs before lazy load, it rewrites the src to the CDN URL, then lazy load moves that CDN URL into data-src and replaces src with a placeholder. That works—but only if the lazy load plugin doesn’t strip attributes it doesn’t recognize.

    How to check which plugin runs first

    View the page source in your browser. Don’t use the inspector—view the raw HTML that the server sends. Look for an <img> tag and check three things:

    • Does the src attribute point to a placeholder or a real image?
    • Does the data-src (or data-lazy-src) attribute exist, and does it point to your CDN or your origin domain?
    • Are there any other data- attributes you don’t recognize? Some plugins add their own.

    If data-src points to your origin domain, lazy load is running before CDN rewrite, and the CDN isn’t serving those images.

    If data-src points to your CDN, the order is correct—but you should also check the Network tab in DevTools to confirm the image actually loads from the CDN and doesn’t 404.

    The fix: force CDN rewrite to run last, or use native lazy loading instead

    Most WordPress lazy load plugins let you set a priority for the output buffer filter. Lower numbers run earlier. If your CDN plugin priority is set to 10 and your lazy load plugin is also 10, WordPress runs them in the order they were loaded—which is unpredictable.

    Set your CDN rewrite plugin priority to something high, like 99 or 999, so it runs after lazy load. That way it catches both src and data-src attributes.

    Alternatively, skip the lazy load plugin entirely and use native browser lazy loading. Add loading="lazy" to your <img> tags. WordPress has done this by default since version 5.5 for any image in post content. Native lazy loading doesn’t rewrite attributes, so there’s no conflict with CDN plugins.

    The tradeoff: native lazy loading doesn’t let you control the threshold (how far from the viewport the image loads), and it doesn’t work in older browsers. But for most solo operators, that’s fine. The performance gain from avoiding two competing JavaScript libraries outweighs the loss of fine-grained control.

    Want more WordPress performance breakdowns like this? Subscribe to One Two Three Send—one article like this, every day, in your inbox.