Skip to content

Notifying customers without becoming spam

  • Home
  • Blog
  • Notifying customers without becoming spam
Notifying customers without becoming spam

Customer notification frequency is the art of sending messages that users actually read—not the ones that end up in the spam folder or get ignored. The key is balancing relevance with urgency: too few notifications miss critical updates, but too many overwhelm users and erode trust. Tools like Postmark, SendGrid, or even basic email APIs in your application (e.g., Laravel’s Mail facade or Node.js’s nodemailer) let you control when and how often you notify customers, but the real challenge is designing a system that scales without breaking trust.

Key Takeaways

  • Frequency matters more than volume: A single poorly timed notification can annoy users more than a daily digest.
  • Context is king: Transactional alerts (order confirmations, password resets) are forgiven; promotional spam is not.
  • Automate with intent: Use workflows (e.g., GitHub Actions for code-based triggers or Zapier for no-code connectors) to send notifications only when they add value.
  • Test before you scale: A/B test notification cadence with a small user segment before rolling out changes.
  • Give users control: Offer opt-outs and preferences—users who can adjust notification frequency are less likely to unsubscribe.
  • Monitor and adapt: Track open rates, unsubscribe requests, and spam complaints to refine your strategy.
  • When in doubt, err on the side of less: It’s easier to send another notification than to win back a frustrated user.
How a customer notification flows from trigger to userA horizontal pipeline showing the stages from event trigger to user action, with decision points for relevance and timing.Customer notification lifecycle1Event trigger(e.g., order placed)2Relevance check(Is this useful?)3Timing(Right now or later?)4Send notification(Email, SMS, in-app)5User action(Open, ignore, unsubscribe)
How a customer notification moves from an event trigger (e.g., an order) through relevance and timing checks before reaching the user. Each step can be automated or manually reviewed depending on your workflow.
---

What is customer notification frequency and why does it matter?

Customer notification frequency refers to how often your business sends updates, alerts, or messages to users—whether via email, SMS, push notifications, or in-app messages. The goal isn’t just to inform but to inform effectively: too few notifications miss critical updates (e.g., a delayed order), while too many overwhelm users and trigger unsubscribe requests or spam complaints. In practice, frequency is about context, not just volume. A single poorly timed notification (e.g., a "Your order is delayed" email sent after the user has already abandoned it) can annoy users more than a daily digest of relevant updates.

Why does this matter in production? Because notification systems are tied to your application’s reliability, user trust, and even legal compliance (e.g., GDPR’s right to be forgotten). Poor frequency leads to high bounce rates (emails marked as spam), lower engagement (users ignoring all messages), and higher support costs (users complaining about "too many emails"). On the flip side, a well-tuned system reduces churn, improves retention, and even drives revenue by keeping users informed about promotions or account activity.

---

When do you actually need to worry about notification frequency?

You need to actively manage notification frequency when your business relies on real-time or near-real-time communication with users. This includes e-commerce platforms (order updates, shipping alerts), SaaS products (account changes, feature releases), subscription services (renewal reminders), and any application where users expect timely updates. If your users are highly engaged (e.g., frequent buyers, power users) or your business operates in a time-sensitive industry (e.g., travel, finance), even minor missteps in frequency can erode trust. Conversely, if your notifications are purely informational (e.g., a blog newsletter) and sent to a broad audience, you have more leeway—but you still risk losing subscribers if the cadence feels arbitrary.

Common red flags that indicate your frequency is off include:

  • Spike in unsubscribe requests: A sudden drop in open rates paired with a rise in "unsubscribe" clicks.
  • Increased spam complaints: Your email provider (e.g., Gmail, Outlook) flags your messages as spam, lowering deliverability.
  • User complaints about "too many emails": Feedback from support teams or direct user messages.
  • Low engagement metrics: High click-through rates (CTR) drop below 2–5% for promotional emails or below 10% for transactional ones.
  • Bounce rate spikes: Hard bounces (invalid email addresses) or soft bounces (full inboxes) exceed 2–3%.

If you’re seeing these signs, it’s time to audit your notification strategy. Tools like Postmark or SendGrid’s deliverability dashboard can help you track these metrics in real time.

---

How does customer notification frequency actually work?

The mechanism behind effective notification frequency is a combination of automation, segmentation, and user preferences. Here’s how it works in practice:

  1. Trigger-based sending: Notifications are sent in response to specific user actions or events (e.g., "order placed," "password reset requested," "account updated"). These are transactional and generally forgiven, even if frequent.
  2. Segmentation: Users are grouped by behavior (e.g., "power users," "occasional buyers," "inactive subscribers") and sent notifications tailored to their activity level. For example, a power user might receive daily digests, while an inactive user gets monthly updates.
  3. Rate limiting and throttling: Systems like GitHub Actions or Zapier can enforce delays between notifications (e.g., "no more than 3 emails in a 24-hour window").
  4. User preferences: Allow users to adjust notification frequency (e.g., "daily," "weekly," "only for important updates") via their account settings. This reduces friction and gives users control.
  5. A/B testing: Experiment with different frequencies (e.g., "send every 2 hours vs. every 4 hours") for a small segment of users to measure engagement before rolling out changes.

Behind the scenes, most systems use a queue-based approach (e.g., Redis lists or Google Pub/Sub) to buffer notifications before sending them. This ensures that even during traffic spikes, your system doesn’t overwhelm users or your email provider’s rate limits.

---

Step-by-step: How to set up a balanced notification frequency

Setting up a balanced notification frequency starts with auditing your current system and then implementing controls. Below is a step-by-step guide to get it right.

  1. Audit your current notifications

    List all notification types (e.g., order confirmations, password resets, promotional emails) and their current frequency. Use your email provider’s analytics (e.g., SendGrid) or a tool like Postmark to track open rates, click-through rates, and unsubscribe requests.

  2. Categorize notifications by type

    Divide notifications into three categories:

    • Transactional: Time-sensitive and critical (e.g., "Your order #12345 is shipping"). These can be frequent.
    • Promotional: Marketing-driven (e.g., "20% off your next purchase"). These should be spaced out.
    • Informational: General updates (e.g., "New blog post published"). These can be batched.
  3. Segment your audience

    Use user data (e.g., purchase history, login frequency) to create segments. For example:

    • Power users: Receive daily digests or real-time alerts.
    • Occasional users: Get weekly summaries.
    • Inactive users: Only notify them for critical updates (e.g., account security).

    Implement this in your application using Laravel’s Eloquent relationships or a database flag (e.g., `user_preferences.notification_frequency`).

  4. Set up rate limiting

    Use a queue system (e.g., Redis or Google Pub/Sub) to throttle notifications. For example:

    // Example: Laravel queue job with rate limiting
    use Illuminate\Support\Facades\Queue;
    use App\Jobs\SendNotification;
    
    Queue::later(now()->addMinutes(5), new SendNotification($user, $message));
    

    Or in Node.js with bull:

    // Example: Bull queue with rate limiting
    const queue = new Bull('notifications', redisUrl);
    queue.add('send_notification', { userId: user.id, message: 'Your order is shipping' }, { delay: 300000 }); // 5-minute delay
    
  5. Enable user preferences

    Add a notification settings page in your application where users can adjust frequency (e.g., "Daily," "Weekly," "Only for important updates"). Store these preferences in the database:

    // Example: Laravel migration for user preferences
    Schema::create('user_preferences', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('user_id')->unique();
        $table->string('notification_frequency')->default('daily');
        $table->timestamps();
    });
    

    Then, modify your notification logic to respect these settings:

    // Example: Check user preference before sending
    $userPreferences = UserPreference::find($user->id);
    if ($userPreferences->notification_frequency === 'weekly') {
        $queue->add('send_notification', ..., { delay: 604800000 }); // 7 days
    }
    
  6. A/B test frequencies

    Use a tool like SendGrid’s A/B testing or Postmark’s split testing to compare two frequencies (e.g., "send every 2 hours vs. every 4 hours") for a small segment of users. Track open rates and unsubscribe requests to determine the better option.

  7. Monitor and adapt

    Set up alerts for:

    • Spike in unsubscribe requests (e.g., >5% in a day).
    • Drop in open rates (<2% for transactional, <1% for promotional).
    • Increase in spam complaints (check your email provider’s dashboard).

    Use Prometheus or Grafana to visualize these metrics and trigger alerts via GitHub Actions or Zapier.

---

Configuration that matters: Key settings to review

Not all notification systems are created equal. Here are the critical configurations to review in your setup:

SettingWhat It DoesExample ValueWhere to Adjust
Rate limitingControls how often notifications are sent to a single user or segment.Maximum 3 emails per user in a 24-hour window.Queue system (Redis, Bull, or your email API).
Bounce handlingDetermines how to handle invalid email addresses (hard bounces) or full inboxes (soft bounces).Soft bounces: Retry 3 times with exponential backoff. Hard bounces: Mark user as inactive.Email provider (SendGrid, Postmark) or custom logic in your app.
User segmentationGroups users by behavior to send relevant notifications.Power users: Daily digests; Inactive users: Monthly updates.Database flags or a segmentation tool (e.g., Segment).
Opt-out preferencesAllows users to adjust or disable notifications entirely.Checkboxes for "Transactional," "Promotional," and "Informational" emails.User account settings in your application.
Deliverability thresholdsTriggers alerts when metrics (e.g., spam complaints) exceed safe limits.Alert if spam complaints exceed 0.1% of sent emails.Email provider dashboard or monitoring tool (Prometheus/Grafana).
---

How to verify your notification frequency is working

Verification starts with testing your system in a staging environment that mirrors production. Here’s how to do it:

  1. Test in staging

    Replicate your production notification workflow in a staging environment. Use tools like Laravel’s test suite or Postman to simulate user actions (e.g., "place order," "reset password") and verify that notifications are sent at the correct frequency.

  2. Check queue delays

    If you’re using a queue system (e.g., Redis), monitor the queue length and processing time. Use redis-cli to check:

    redis-cli LRANGE notifications:user_123 0 -1
    

    Or in Google Pub/Sub:

    gcloud pubsub subscriptions pull notifications-sub --limit=10
    
  3. Review user preferences

    Log in as a test user and adjust notification frequency. Verify that the system respects these settings by checking your inbox or a test email account.

  4. Monitor deliverability

    Use your email provider’s dashboard (e.g., SendGrid) to check:

    • Open rates (should be >2% for transactional, >1% for promotional).
    • Spam complaints (should be <0.1%).
    • Bounce rates (should be <3%).
  5. Simulate traffic spikes

    Use tools like Locust or k6 to simulate high traffic (e.g., 1,000 concurrent users placing orders). Monitor your queue system and email provider for throttling or delays.

---

Failure modes and how to debug them

Even well-designed notification systems can fail. Here are the most common issues and how to diagnose them:

  1. Notifications are sent too frequently

    Symptoms: Users complain about "too many emails," open rates drop, or unsubscribe requests spike.

    Diagnosis:

    • Check your queue system for backlogged jobs (e.g., redis-cli LRANGE).
    • Review your segmentation logic—are power users being over-notified?
    • Audit your email provider’s deliverability dashboard for spam complaints.

    Fix:

    • Implement stricter rate limiting (e.g., 1 notification per user per hour).
    • Add a "last sent" timestamp to your database and enforce delays.
    • Use A/B testing to find the optimal frequency for each segment.
  2. Notifications are delayed or lost

    Symptoms: Users report not receiving critical alerts (e.g., order updates), or queue jobs pile up.

    Diagnosis:

    • Check your queue system for failed jobs (e.g., bull --queue notifications --failed).
    • Monitor your application logs for errors in the notification service.
    • Test with a tool like Postmark’s debug tool to verify delivery.

    Fix:

    • Increase queue worker concurrency (e.g., QUEUE_WORKER=4 in Laravel).
    • Add retries with exponential backoff to your queue jobs.
    • Use a more reliable queue system (e.g., Google Pub/Sub instead of Redis).
  3. Users are marked as spamSymptoms: Your email provider flags messages as spam, and deliverability drops.

    Diagnosis:

    • Check your email provider’s spam score (e.g., SendGrid’s deliverability tools).
    • Review your subject lines and content for spam triggers (e.g., "FREE," "URGENT," excessive links).
    • Audit your IP reputation (e.g., MXToolbox).

    Fix:

    • Clean your email list (remove inactive users).
    • Use a dedicated transactional email service (e.g., Postmark) with a warm-up period.
    • Avoid promotional language in transactional emails.
  4. User preferences are ignored

    Symptoms: Users report receiving notifications despite opting out or adjusting frequency.

    Diagnosis:

    • Check your database for inconsistent user preference records.
    • Review your notification logic for hardcoded overrides.
    • Test the user interface—are preferences being saved correctly?

    Fix:

    • Add validation to ensure user preferences are always respected.
    • Log preference changes and notify users when their settings are applied.
    • Use a transactional database (e.g., PostgreSQL) to avoid race conditions.
---

Cost and operational overhead of managing notification frequency

The cost of managing notification frequency isn’t just in tools or infrastructure—it’s in time, reliability, and user trust. Here’s what to consider:

  1. Tooling costs

    Basic email APIs (e.g., Laravel’s Mail facade or Node.js’s nodemailer) are free but lack advanced features like deliverability tracking or A/B testing. Paid services like Postmark or SendGrid add subscription costs and scale with usage (e.g., per thousand emails). Queue systems like Redis or Google Pub/Sub add minimal cost but require operational overhead.

  2. Operational overhead

    Monitoring and maintaining a notification system requires:

    • Regular audits of segmentation and frequency rules.
    • Alerting for spikes in bounces or spam complaints.
    • Testing changes in staging before production.

    For small teams, this can be managed part-time, but as volume grows, you’ll need dedicated resources or automation (e.g., GitHub Actions for monitoring).

  3. User trust

    The biggest cost is lost engagement. A poorly managed notification system can:

    • Drive users to unsubscribe (costing future revenue).
    • Damage your brand reputation (e.g., "spammer" label).
    • Increase support costs (users complaining about "too many emails").

    In practice, the cost of fixing a broken notification system (e.g., re-engaging users, cleaning your email list) is often higher than the initial setup.

  4. Scalability

    As your user base grows, notification frequency becomes harder to manage. Solutions like:

    • Dynamic segmentation: Adjust frequencies based on real-time user behavior (e.g., "users who haven’t logged in in 30 days get fewer emails").
    • Machine learning: Use tools like Segment or Postmark’s automation to predict optimal frequencies.
    • Multi-channel notifications: Spread alerts across email, SMS, and in-app messages to reduce frequency per channel.

    These require more complex infrastructure but pay off as you scale.

---

Security considerations for notification systems

Notification systems are a prime target for abuse—whether it’s credential stuffing (sending fake notifications to reset passwords), spam campaigns (exploiting your email infrastructure), or data leaks (sending sensitive info to the wrong user). Here’s how to protect your system:

  1. Rate limit API endpoints

    Use tools like GitHub Actions or Cloudflare WAF to limit requests to your notification API (e.g., "no more than 100 requests per minute per IP").

  2. Validate user input

    Ensure that notification triggers (e.g., "password reset") come from trusted sources. For example:

    // Example: Laravel middleware to validate reset requests
    public function handle($request, Closure $next) {
        $ip = $request->ip();
        if (!in_array($ip, $this->allowedIPs)) {
            abort(403, 'Unauthorized');
        }
        return $next($request);
    }
    
  3. Encrypt sensitive data

    Never send sensitive info (e.g., credit card numbers, passwords) in plain text. Use AWS KMS or Google Cloud KMS to encrypt data before sending.

  4. Monitor for abuse

    Set up alerts for:

    • Sudden spikes in notification requests (
      1. Unusual patterns (e.g., "100 password reset requests from the same IP in 5 minutes").
      2. Use tools like Prometheus to track API call rates and trigger alerts via GitHub Actions.
    • Secure your email infrastructure

      If you’re using a custom SMTP server, ensure it’s hardened against:

      • Open relays: Only allow emails to be sent from your server’s IP.
      • SPF/DKIM/DMARC: Configure these records to prevent spoofing. For example:
      v=spf1 include:_spf.yourdomain.com ~all
      
    • Rate limiting at the SMTP level: Use Postfix or Exim to limit connections per IP.

    For simplicity, use a managed service like SendGrid or Postmark, which handle these security layers for you.

---

Common mistakes and how to avoid them

Even experienced teams make these pitfalls when managing notification frequency. Here’s how to spot and fix them:

  1. Assuming "more is better"

    Mistake: Sending notifications without considering user context (e.g., sending a daily digest to a user who only logs in monthly).

    Fix:

    • Always segment users by behavior (e.g., "active," "inactive," "power user").
    • Use A/B testing to validate frequencies before rolling out changes.
    • Monitor engagement metrics (open rates, clicks) to adjust dynamically.
  2. Ignoring user preferences

    Mistake: Overriding user-selected notification frequencies (e.g., sending daily emails to a user who opted for weekly).

    Fix:

    • Store preferences in the database and enforce them in your notification logic.
    • Add a "last updated" timestamp to preferences to avoid stale settings.
    • Notify users when their preferences are applied (e.g., "Your email frequency has been updated to weekly").
  3. Not testing in staging

    Mistake: Deploying notification changes to production without verifying they work in staging.

    Fix:

    • Replicate your production environment in staging (e.g., use Docker or Terraform for IaC).
    • Test edge cases (e.g., "What happens if 1,000 users opt out simultaneously?").
    • Use tools like Locust to simulate traffic spikes.
  4. Overcomplicating the system

    Mistake: Using a complex queue system (e.g., Google Pub/Sub) when a simple database flag would suffice.

    Fix:

    • Start with a lightweight solution (e.g., Redis lists or a database column for "last_notified_at").
    • Only scale up if you hit bottlenecks (e.g., queue backlogs or delays).
    • Use managed services (e.g., SendGrid) for email delivery to avoid operational overhead.
  5. Treating all notifications equally

    Mistake: Applying the same frequency rules to transactional alerts (e.g., "order shipped") and promotional emails (e.g., "20% off").

    Fix:

    • Categorize notifications by type (transactional, promotional, informational).
    • Apply stricter limits to promotional emails (e.g., "no more than 1 per week").
    • Use separate queues or channels for different notification types.
---

A realistic scenario: E-commerce order notifications

Let’s walk through a concrete example: an e-commerce store using Laravel and Postmark for notifications. The goal is to notify customers about order updates without overwhelming them or triggering spam complaints.

>> excerpt: Set up Kubernetes HPA that actually holds in production, with real limits and sensible fallbacks that stop your cluster from falling over. meta_title: Kubernetes HPA Setup: Autoscaling That Actually Holds meta_description: Set up Kubernetes HPA with real production limits, sensible fallbacks, and a verification routine that stops your cluster falling over. Learn which metrics matter and what breaks first. meta_keywords: Kubernetes HPA, horizontal pod autoscaler, pod autoscaling, Kubernetes autoscaling, HPA setup, K8s autoscaling tags: kubernetes, autoscaling, hpa, reliability <<>>

The Kubernetes Horizontal Pod Autoscaler (HPA) scales replicas based on observed CPU, memory, or custom metrics. In production, the HPA alone is not a set-and-forget tool — you need sensible min/max limits, a fallback metric that prevents scale-down loops, and a verification routine that confirms your change actually holds when traffic hits.

Key Takeaways

  • Always set a minReplicas that can handle your baseline traffic and a maxReplicas that respects your cluster's total capacity.
  • Use averageUtilization for CPU and memory, not absolute values, so the autoscaler works across different pod sizes.
  • Add a custom metric (like requests per second or queue length) as a secondary scaling signal to avoid CPU-only thrash.
  • Set a downscale stabilization window of at least five minutes to stop the HPA from reacting to brief traffic dips.
  • Monitor the HPA status and events; the conditions field tells you exactly why it is not scaling.
  • Test your HPA with a load generator before you rely on it for a production event.
  • Understand the difference between HPA and cluster autoscaler; they work together but solve separate problems.
How the HPA makes a scaling decisionFlow chart showing metric collection, calculation of desired replicas, and the scaling action.HPA decision flow1Metricscollectionkubelet /custom API2Desiredreplicasceil(current *metric/target)3Scaledecisionabove/belowmin/max?4Scalereplicaspatch scalesubresource
The HPA collects metrics from the metrics-server or a custom API, calculates the desired replica count, checks it against your min and max limits, and then patches the scale subresource.

Why an HPA without sensible limits is a production incident waiting to happen

The HPA exists to handle variable load, but it is not magic. The default behaviour is reactive, not predictive. If you set minReplicas=1 and maxReplicas=10, a traffic spike will scale up — eventually. A sudden drop will scale down — eventually. But without careful limits, you can scale down to one pod just as the next burst arrives, and your users see 503s while the new pod warms up. In practice, that cold start can take ten seconds or more, depending on the application.

We have been burned by this. A client set their HPA with a CPU target of 80% and no memory limit. The cluster had plenty of headroom. The application was a Laravel API that served a daily batch job. The batch job pushed CPU to 90% for exactly three minutes, the HPA scaled up to ten pods, the batch finished, and the HPA scaled back down to one. The next batch job started twenty minutes later, and every pod was cold. The first request of each batch timed out. The fix was a minReplicas of three and a memory limit that forced the batch to run on dedicated pods.

When you actually need HPA (and when you do not)

Use HPA when your traffic pattern is genuinely variable — daily peaks, batch jobs, seasonal spikes, or a growth trajectory that you cannot predict. Do not use HPA for a steady-state workload. If your traffic is predictable, you are better off with a fixed replica count and a Cluster Autoscaler that adds nodes when you run out of capacity. The HPA adds complexity: you need metrics-server, a working custom metrics API if you use it, and the cognitive load of tuning target values. For a simple blog or a static site, you do not need HPA. A fixed number of replicas and a load balancer is simpler and safer.

How the HPA actually works under the hood

The HPA controller runs in the control plane. It queries the metrics API every 15 seconds by default (the --horizontal-pod-autoscaler-sync-period flag controls this). For each target metric, it computes the desired replica count as ceil(currentMetricValue / targetMetricValue * currentReplicas). It then takes the maximum of all computed desired replicas and applies that value, bounded by minReplicas and maxReplicas. The controller ignores scale-down recommendations for a period defined by the downscale stabilization window, which defaults to five minutes. This prevents thrash, but it also means a traffic spike that lasts two minutes will not trigger a scale-down at all.

Step-by-step: set up HPA that holds

  1. Deploy metrics-server if you are not using a managed Kubernetes service that provides it. For self-managed clusters, use the official manifest from the metrics-server repository.
  2. Define your Deployment with resource requests and limits. The HPA cannot calculate utilization without them. Use requests that reflect your pod's steady-state consumption and limits that cap its maximum.
  3. Create the HPA manifest. You can use kubectl autoscale to generate a starting point, but write a manifest for repeatability.
  4. Apply the HPA and verify it is working with kubectl get hpa -w. Watch the TARGETS column to see current utilization.
  5. Generate load with a tool like wrk or vegeta to confirm the HPA scales up within your expected time.
  6. Tune the stabilization window if the default five minutes does not suit your traffic pattern.
  7. Set up alerts on the HPA status. A common condition is AbleToScale and ScalingActive. If either is False, the HPA is not doing its job.

Configuration that matters — not just CPU and memory

CPU and memory are the default metrics, and they are often sufficient for request-processing workloads. But for a queue worker or a streaming application, a custom metric is better. The HPA can scale on any metric exposed by the custom.metrics.k8s.io or external.metrics.k8s.io API. For example, you can scale based on the depth of an SQS queue or the number of unprocessed messages in a Redis stream. The key is that the metric must be available in the metrics API and the target value must be expressed in the same unit. The Kubernetes HPA documentation has a full reference on the metrics API.

The downscale stabilization window is set using the behavior field, added in Kubernetes 1.18. You can set separate policies for scale-up and scale-down. A production-safe setup uses a five-minute downscale window and a 30-second upscale window. The upscale window is short to respond quickly to traffic, and the downscale window is long to avoid thrash.

HPA configuration comparisonsRows comparing the default HPA setup, a setup with custom metrics, and one with behavior policies.HPA setup comparisonDefaultCPU and memory only, 5 minute downscale, no custom metricsCustom metricsScale on queue length or RPS, requires metrics API setupBehaviorFine‑grained policies, stabilization windows, rate limits
Comparison of the default HPA setup, one with custom metrics, and one with fine‑grained behavior policies for production control.

How to verify your HPA works before you rely on it

You verify an HPA by generating load and watching it scale. Use a tool like kubectl run load-generator --image=busybox -- /bin/sh -c "while true; do wget -q -O- http://your-service; done" to simulate traffic. Then watch kubectl get hpa every few seconds. The TARGETS column should move toward your configured value. If the target never increases, your requests might be too low. If the target stays high and replicas do not increase, check the events: kubectl describe hpa will tell you if the metric is unavailable or if the HPA is stuck at maxReplicas.

Failure modes and how to debug them

The most common failure is the HPA reporting "missing request for CPU" — you have not set resource requests on your pods. The second most common is a throttled metrics-server, which happens when you have many pods and the metrics-server cannot keep up. The symptoms: the HPA reports "unable to get metrics" and stops scaling. The fix is to increase the metrics-server resources or adjust its scrape frequency. The third failure is the HPA scaling down too aggressively because a short traffic dip caused the metric to drop. This is solved by increasing the downscale stabilization window. The metrics-server documentation covers tuning for large clusters.

Cost and operational overhead

The HPA itself costs nothing beyond the control plane resources it consumes, which are negligible for most clusters. The operational overhead is the tuning: you need to know your application's baseline CPU and memory consumption, and you need to decide on target values that balance responsiveness with stability. The real cost is the wasted cluster resources when the HPA scales too aggressively, or the lost user requests when it scales too slowly. A common mistake is to set the CPU target too low, which causes the HPA to scale up prematurely and waste money. We recommend starting with 70% utilization and adjusting based on observed performance.

Security considerations

The HPA does not need special permissions beyond what the controller already has. However, the custom metrics API can expose sensitive information if you are not careful. Ensure that the metrics-server or your custom metrics adapter is secured with TLS and role-based access control. The Kubernetes authentication documentation covers securing the API.

Common mistakes we see in production

  • Setting maxReplicas too low. If you set maxReplicas to 5, but a traffic spike requires 10, your service fails. Base maxReplicas on your cluster's total capacity, not on your cost optimisation.
  • Using a single metric. CPU and memory alone are not enough for applications that are I/O-bound or have a long startup time. Add a custom metric or use a scheduled scaling solution like KEDA.
  • Forgetting to test. An untested HPA is as good as no HPA. Test with load and confirm that the scale-up and scale-down behaviour match your expectations.
  • Ignoring the HPA status. The HPA will tell you why it is not scaling if you read the events. Ignoring that leads to a production incident.

A concrete scenario: batch processing API

A client runs a Laravel API that processes a queue of image transformations. The load is unpredictable: sometimes it is idle, sometimes it handles a batch of 10,000 images. We set the HPA with minReplicas=2 to handle baseline health checks and slow traffic, and maxReplicas=20 to handle the largest batch. The primary metric was CPU, but we added a custom metric for queue length using the Redis metrics adapter. The downscale window was set to 10 minutes because the batch processing can take 5 minutes, and we did not want the HPA to scale down in the middle of a batch. The result was a system that scaled up within 30 seconds of a large batch arriving and scaled down slowly, avoiding cold starts.

HPA scaling timeline for a batch processing APITimeline showing traffic arrival, HPA reaction, scale-up, batch processing, and scale-down.Batch processing scaling timeline0m2m4m6m8m10mBatch arrivesHPA reactionCPU > targetscale-up startsScale-up2 → 12 replicasbatch processesBatch doneCPU dropsscale-down waitsScale-downslowly, over5-10 minutes
Timeline of the batch processing scenario: the batch arrives, the HPA reacts, scales up, and then scales down slowly over five to ten minutes.

Alternatives compared

ApproachBest forDownside
HPA (CPU/memory)Simple request‑processing workloadsReactive, can thrash on short bursts
HPA (custom metrics)Queue workers, streaming appsRequires metrics API setup and tuning
KEDAEvent‑driven scaling (scalers for many sources)More complex, external dependency
Fixed replicas + Cluster AutoscalerSteady‑state or predictable trafficCannot react to sudden spikes without headroom
Vertical Pod Autoscaler (VPA)Optimising resource requestsDoes not change replica count, can cause restarts

In short, a production‑ready HPA uses resource requests, a sensible min and max, a downscale stabilization window, and a verification routine. Tuning is an ongoing process, not a one‑off setup. Start with CPU and memory, add a custom metric if your application is I/O‑bound, and always test with real load.

People also search for

Our team can help you set up autoscaling that actually holds. Contact us for a review of your current setup, or see our DevOps and cloud services for a wider picture of what we handle.

Frequently asked questions

  • There is no universal number. Transactional messages such as order confirmations and password resets should send immediately; marketing broadcasts usually work at weekly or biweekly intervals. Track unsubscribe and spam complaint rates per campaign. Keep complaint rate below 0.1% and adjust frequency when it rises.

  • Transactional messages are triggered by a user action and often exempt from marketing consent rules, but still need an unsubscribe link. Marketing mail requires explicit opt-in. Use separate sending domains and IP pools so poor marketing engagement does not degrade deliverability of password resets and receipts.

  • Store last_sent_at and per-user daily or weekly limits in application config. Check the cap before enqueueing; use a queue with throttling such as Redis counters or Laravel’s throttle middleware. Verify by querying send logs for repeated messages to one address inside the window.

  • Add a List-Unsubscribe header with a one-click endpoint as defined in RFC 8058, plus a visible unsubscribe link in the footer. Remove the address from the campaign list immediately and add it to a suppression list. Test with a mail header inspector before sending.

  • Enrol in ISP feedback loops such as Google Postmaster Tools, Yahoo CFL and Microsoft JMRP. Compute spam complaint rate as complaints divided by delivered messages. Below 0.1% is healthy; above 0.3% usually triggers filtering. Investigate per-recipient actions in your sending logs.

  • Sender reputation is scored from domain and IP sending history: bounce rate, spam complaints, engagement. High frequency with low opens or many complaints lowers it, causing inbox providers to junk or throttle mail. Use a dedicated subdomain for bulk sends and monitor reputation in Postmaster Tools.

  • Publish an SPF TXT record authorising your sending host, a DKIM selector with the public key, and DMARC policy starting at p=none. Verify records with dig or an online validator, then send a test and check the Authentication-Results header in Gmail shows pass for all three.

  • Quiet hours suppress non-urgent messages during a user’s local night. Store each user’s timezone and preferred window, and delay marketing sends until the next window. Transactional alerts like password resets ignore quiet hours. This reduces immediate unsubscribes from late-night pings.

  • Warm up new sending IPs by ramping volume, segment engaged recipients, avoid URL shorteners, include a plain-text part, keep From name and domain consistent, remove stale addresses, and monitor bounces. Test a sample with SpamAssassin or an email tester before the full send.

  • Message volume, deliverability tooling such as dedicated IPs and monitoring, list hygiene, suppression storage, and engineering time for frequency caps and a preference centre drive cost. Spend scales with send rate, so unused capacity is waste. For a review of your current setup, see /contact.

0 comments

Be the first to share your thoughts.

Leave a comment

Chat on WhatsApp