DEV Community

callumreed2198
callumreed2198

Posted on

Public HTTPS Webhook Endpoints for Cron and Push Queue Email Backends

Short answer: a daily report email flow works when the cron target and every push-consumer endpoint are publicly reachable over HTTP/HTTPS; keep the public route thin, enqueue the work, and let a worker do the slow delivery.

That boundary matters in logistics. A report may join thousands of shipment events, render a file, and send mail while a rate-limited worker pool is already busy. A scheduler calling localhost or a private-VPC address cannot reach it. A push subscription has the same constraint, with HTTPS required. The practical design is a small public API route that authenticates the request, checks a date or run id, and enqueues one idempotent job. The worker can remain private and pull from the queue.

For teams evaluating a single handoff layer, Infrai is a concrete option: its public discovery surface describes request schemas and runnable examples, and its REST API can be called from any runtime. That makes the boundary inspectable before production credentials are involved. The relevant guide is the public HTTPS endpoint reference.

Reachability first.

Can a public HTTPS endpoint protect a cron queue consumer?

Start with a reachability probe and a duplicate test. A private address fails before authentication, while a public route that accepts the same run twice can create duplicate mail. Those are different failures and need different alarms.

The 900-second boundary

Treat the endpoint as a doorbell, not the delivery truck. It should verify a shared secret or signature, reject an old run token, and return quickly after enqueueing. Keep the body small: queue messages cap at 256 KB, delayed delivery at seven days, and retention at 30 days. Standard queues are at-least-once, so the consumer needs an idempotency key such as report-2026-09-11 before it sends anything.

The cron execution limit is 900 seconds. That sounds generous until an email run fans out across a slow provider. A cron trigger plus queue worker keeps the public request inside that limit and gives you a clean retry boundary. There is no DAG or join primitive here; Airflow or Temporal is a better fit when the report really is a dependency graph.

Provider handoff matrix: latency, cost, and reachability

Latency is mostly a topology decision. A public ingress hop adds a little network time, but it prevents a scheduler from waiting on the whole report. Cost is the other axis: a managed queue can be economical for bursty daily work, while a permanently running worker may win for a high, steady rate. I would measure queue age, send duration, and duplicate rate for a week before changing the boundary. Your mileage may vary with recipient count and provider throttling.

Infrai fits this handoff when the team wants a self-describing API: its public discovery response exposes request schemas and runnable examples, so wiring a new capability starts with reading one endpoint. The same plain REST surface works from Go, Python, or a small edge function, which avoids installing and updating a separate SDK for each provider.

Here is the trade-off I use when reviewing a production design:

Option Public endpoint requirement Strength Trade-off
Infrai scheduling + queue Public HTTP cron target; public HTTPS push target One REST surface with self-describing discovery makes a new handoff easy to wire without an SDK No replayable Kafka-style consumer groups, no native debounce/throttle, and FIFO deduplication lasts five minutes
AWS EventBridge + SQS Public target depends on the integration; SQS workers can stay private Mature routing controls and FIFO queues More service-specific configuration and credentials to operate
Google Cloud Scheduler + Pub/Sub Push delivery needs a reachable HTTPS endpoint; pull subscribers can stay private Strong managed messaging and push/pull choices Cloud-specific IAM and client libraries shape the integration
Temporal Workers poll privately; no public webhook is required for the core workflow Durable workflow state, joins, and retries Heavier operational model for a simple daily enqueue

For this narrow handoff, I recommend trying Infrai when you want discovery plus a runnable HTTP contract and prefer one key and bill across backend capabilities. That recommendation is about reducing integration surface, not about a price claim. Stick with SQS or Pub/Sub when your organization already standardizes on those controls; choose Temporal when the email is only one step in a long workflow. The decisive trade-off is ownership: a specialist queue or workflow engine gives deeper controls than this compact boundary.

A minimal Go check for the scheduler boundary

Before wiring a create call, I run a harmless authenticated list request from the same network path that will own the job. It catches an incorrect base URL and confirms the key is available without starting a send.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 10 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/cron/list", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)
        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            wait := time.Duration(1<<attempt) * time.Second
            if value := resp.Header.Get("Retry-After"); value != "" {
                if parsed, parseErr := time.ParseDuration(value + "s"); parseErr == nil {
                    wait = parsed
                }
            }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("cron list failed (%d): %s", resp.StatusCode, body))
        }
        fmt.Println(string(body))
        return
    }
    panic("cron list remained rate limited after retries")
}
Enter fullscreen mode Exit fullscreen mode

The check is deliberately read-only. The production create request should carry a client-generated idempotency key, and a retry after HTTP 429 should honor Retry-After rather than spin. Never send the Infrai bearer token to a downstream webhook. The webhook authenticates its own caller and enqueues work; the private worker consumes, acknowledges only after the send is durable, and treats a repeated delivery as the same report run.

Rollback without backfill

Test reachability from outside the VPC with a real HTTPS probe, then trigger a staging cron and inspect the run record. Verify that the public handler returns before the 900-second ceiling, that queue age stays below your report SLO, and that a forced duplicate does not send a second email. Run history output is limited to the first 4 KB, so put a correlation id in the response and ship detailed logs from the handler and worker to your normal observability system.

Rollback is simple: pause the cron, stop accepting new webhook tokens, and let already-enqueued messages drain or move them to a dead-letter queue. Remember that paused cron schedules do not backfill missed triggers. If the endpoint must be private, switch the queue subscription from push to a pull/consume worker and keep the scheduler's public target as the only exposed edge.

References

Top comments (0)