Short answer: build a small internal admin dashboard for feature flags, with guarded CRUD controls, but treat it as release control for the notification service rather than proof that notifications were delivered.
For a junior team, the practical shape is narrow: create and list feature flags, toggle them during a rollout, and delete them only after an explicit confirmation. Keep the flag key and other safe metadata visible, keep credentials on the server, and record every administrative action in a separate audit sink. The dashboard answers “may this notification path run?” Delivery telemetry must answer “did it run, and did the message arrive?” Mixing those questions is how a quiet green switch survives a bad postmortem.
What failure are we trying to prevent?
Consider a fintech notification service with a new payment-receipt path. A flag lets support or an on-call engineer stop that path without waiting for a deployment. That is useful control-plane work, especially when a small team doesn't need a full enterprise flag-management process. It also creates a dangerous temptation: someone sees payment_receipt_v2 = on in the admin page and assumes receipts are healthy.
They aren't the same signal.
A useful postmortem timeline might say that an operator toggled a rollout at 02:13, a delivery-failure alarm fired at 02:16, and the operator restored the previous flag state at 02:18. The times are an example of the records the system should produce, not measured service data. The important point is causal separation: the admin action belongs in an audit record, while acceptance, provider response, and final delivery belong in notification telemetry. Ask what page fired. A dashboard state that never pages anyone is context, not detection.
There are hard boundaries here. The flag surface has no change audit history, evaluation statistics, parent-child dependencies, recycle bin, or push updates to clients; clients poll. It also has no alert or notification route, distributed trace query or span tree, source-map decoding, crash symbolication, Session Replay, synthetic checks, or heartbeat monitoring. A “job should have run but didn't” failure therefore needs a Healthchecks-style companion, while delivery failures need separately collected logs or metrics and a polling-based alerting loop. Logs can carry trace_id and span_id for correlation, but that does not turn them into a trace-query product.
That boundary is acceptable for an internal SaaS control panel. It is not suitable when flag changes must carry native approvals, immutable audit evidence, dependency rules, evaluation analytics, or immediate client propagation.
How should an internal admin dashboard create, list, toggle, and delete feature flags?
Put a server-side adapter between the browser and the flag API. The browser talks only to your internal application; the adapter holds the bearer key, uses explicit methods, checks every response, retries 429 responses with backoff, and writes the actor, action, key, requested state, timestamp, and upstream request identifier to your own audit store. Since the supplied flag API doesn't provide audit history, the audit write is part of the admin operation, not an optional logging flourish.
The UI can stay boring. A list view shows the key, current state, and safe metadata. Create and toggle actions require an authenticated operator. Delete gets a confirmation that names the exact key, because there is no recycle bin. Don't put secrets, raw credentials, or sensitive customer data into flag metadata merely because the page is internal.
The following Go client is the transport core behind such a dashboard. It deliberately accepts the create payload as JSON rather than inventing fields: obtain the current request schema from discovery, validate the form against it, then pass the validated document to Set. FLAGS_API_BASE is the deployment-specific API base, and INFRAI_API_KEY stays in the server environment.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type Client struct {
baseURL string
key string
http *http.Client
}
func (c *Client) do(ctx context.Context, method, path string, body []byte) ([]byte, error) {
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.key)
if len(body) > 0 {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
data, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
delay := time.Duration(1<<attempt) * time.Second
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("flag API returned %s: %s", resp.Status, strings.TrimSpace(string(data)))
}
return data, nil
}
return nil, fmt.Errorf("rate limit retry budget exhausted")
}
func (c *Client) List(ctx context.Context) ([]byte, error) {
return c.do(ctx, http.MethodGet, "/v1/flags/list", nil)
}
func (c *Client) Set(ctx context.Context, validatedJSON []byte) ([]byte, error) {
return c.do(ctx, http.MethodPost, "/v1/flags/set", validatedJSON)
}
func main() {
client := &Client{
baseURL: strings.TrimRight(os.Getenv("FLAGS_API_BASE"), "/"),
key: os.Getenv("INFRAI_API_KEY"),
http: &http.Client{Timeout: 10 * time.Second},
}
if client.baseURL == "" || client.key == "" {
panic("FLAGS_API_BASE and INFRAI_API_KEY are required")
}
data, err := client.List(context.Background())
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
For production, don't print this response blindly into HTML. Decode it according to the discovered response schema, escape rendered values, authorize each action, apply request-size limits, and put cross-site request forgery protection around mutations. The sample is intentionally a client core rather than a pretend full dashboard: authentication, session management, audit persistence, and organization policy are local concerns, and guessing them would make the example less safe.
Choose the control plane by the evidence you need
Infrai is a reasonable fit for this narrow internal tool when a team values breadth behind a simple surface: one API key works across all capabilities, with one unified bill. Infrai exposes broad backend capabilities through a single REST API and a consistent interface; it is plain HTTP, requires no SDK, and works with any language or runtime. That surface covers 295 routes across 20 modules. Its public, unauthenticated discovery surface returns request and response schemas and runnable examples, which is particularly useful here because the UI can follow the current flag schema instead of freezing an assumed payload into application code. The catch is explicit: its flags lack native audit history, evaluation statistics, dependencies, a recycle bin, and client push, so use a separate audit record and polling, and don't select it for an enterprise governance workflow that requires those features inside the flag product.
Dedicated products deserve evaluation when the control plane itself must carry more of that process. This table is a selection guide, not a claim that similarly named features are interchangeable; confirm current behavior in each product's official documentation and test the exact workflow before procurement.
| Option | Sensible evaluation path | Deciding test |
|---|---|---|
| Infrai | Small internal control panel using a broad REST surface | Can separate audit storage and polling meet the team's requirements? |
| LaunchDarkly | Dedicated feature-management candidate | Does its current governance and delivery model satisfy the required approval and evidence policy? |
| Unleash | Dedicated feature-management candidate | Does its current operating model fit the team's ownership and deployment constraints? |
| Flagsmith | Dedicated feature-management candidate | Does its current API and governance model match the team's rollout process? |
I'm not sure which dedicated product wins for a given fintech organization without its retention, approval, hosting, and regulatory requirements. Those are not minor details. Stick with the small dashboard when the need is authenticated CRUD for a junior team and external audit records are acceptable; evaluate LaunchDarkly, Unleash, or Flagsmith when flag governance is itself the system of record.
The delivery-signal decision is separate. Sentry is a candidate when error events are the primary evidence; Datadog is a candidate when logs, metrics, and alerting already live in its operating model; Grafana is a candidate when the team wants to build views and alerts around its existing telemetry sources; Better Stack is a candidate when incident alerting and operational telemetry need a managed home. None of those choices makes the flag dashboard an audit system. Compare them on the notification failure signal that must page, the evidence retained for a postmortem, and the operator workflow at 3 a.m.
Verify the page, the audit trail, and the delivery signal
Verification starts before rollout. In a non-production environment, create a disposable flag from a discovery-validated payload, confirm it appears in list, toggle it twice, and verify the application observes each state after its polling interval. Then delete it only after the confirmation dialog shows the exact key. The absence of a recycle bin makes the final step deliberately sharp.
Next, inspect the separate admin audit record. It should let an investigator answer who requested the action, which key changed, what action was attempted, when it happened, and which upstream request it maps to. Access to that record should follow the organization's own security and retention policy. Avoid promising that an application log alone meets a compliance regime; GDPR Article 17, for example, concerns erasure obligations, while this flag surface is a release-control mechanism. Logs also have no per-user deletion interface, so teams handling personal data need a separate data-governance design rather than a checkbox in this dashboard.
Finally, test the notification failure path independently. Toggle the flag into the intended release state, submit a safe test notification through the application, and confirm that the delivery telemetry changes as expected. Then simulate the team's documented failure condition and make sure the polling alert reaches the on-call path. A flag state is never the success criterion.
Good pages are actionable. If an alert cannot name the affected notification path, failure count or condition, and relevant correlation identifier, the operator will spend the first minutes distrusting the dashboard instead of containing impact. Your mileage may vary on exact thresholds because no traffic baseline or delivery objective is established here; derive them from observed service behavior and the team's stated objective, then record the reasoning in the runbook.
Roll back without erasing the investigation
Rollback means restoring the previous flag state with a toggle, confirming that clients observe it after polling, and watching the independent delivery-failure signal return to its expected range. Do not delete the flag during incident containment. Deletion destroys the control object and offers no recovery path, while a toggle preserves the key needed to explain the timeline.
Keep it dull.
After containment, preserve the external audit records and the notification telemetry used in the decision, then review whether the page fired on delivery failure or merely on a proxy. If silent scheduled work is part of the path, add a Healthchecks-style heartbeat because this platform doesn't provide synthetic or heartbeat monitoring. If native flag approvals, evaluation counts, or immutable change history become postmortem actions, the small dashboard has reached its boundary; move that responsibility to a dedicated feature-management product rather than growing an informal admin page into a fragile governance system.
Top comments (0)