Accept result messages from a lab, keep the original, store observations once per revision, alert the ordering clinician about critical values, escalate when nobody acknowledges them, and release reviewed reports to the patient portal, built by hand on AWS and in Go with Encore.

A clinic or telehealth app that orders lab tests gets results back as a stream of messages from the lab: a report per accession number, a set of observations per report, and corrections that arrive hours or days later. Two of those results matter more than the rest. A critical potassium value has to reach the ordering clinician within minutes, and a routine panel should only reach the patient once a clinician has looked at it. When both depend on someone refreshing an inbox, a critical result can sit unread over a weekend.
This guide builds the backend that receives results, keeps the originals, alerts on critical values, escalates when nobody acknowledges them and releases reviewed reports, first as AWS pieces by hand and then in Go with Encore. Compliance requirements for health data depend on your jurisdiction and contracts and are not covered here. The PHI audit log guide covers recording who read which record.
The schema is the same in both builds:
CREATE TABLE lab_reports (
id BIGSERIAL PRIMARY KEY,
lab TEXT NOT NULL,
accession TEXT NOT NULL,
revision INT NOT NULL, -- 1, then 2+ for corrected results
patient_id TEXT NOT NULL,
clinician_id TEXT NOT NULL,
raw_key TEXT NOT NULL, -- the message as received, in object storage
critical BOOLEAN NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
critical_acked_at TIMESTAMPTZ,
critical_acked_by TEXT,
escalated_at TIMESTAMPTZ,
released_at TIMESTAMPTZ,
released_by TEXT,
UNIQUE (lab, accession, revision)
);
CREATE INDEX lab_reports_unacked ON lab_reports (received_at)
WHERE critical AND critical_acked_at IS NULL AND escalated_at IS NULL;
CREATE TABLE alert_outbox (
report_id BIGINT PRIMARY KEY REFERENCES lab_reports (id),
clinician_id TEXT NOT NULL,
codes TEXT NOT NULL, -- comma-separated LOINC codes
claimed_at TIMESTAMPTZ,
published_at TIMESTAMPTZ
);
CREATE INDEX alert_outbox_pending ON alert_outbox (report_id)
WHERE published_at IS NULL;
CREATE TABLE sent_alerts (
report_id BIGINT NOT NULL,
kind TEXT NOT NULL,
claimed_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
PRIMARY KEY (report_id, kind)
);
CREATE TABLE observations (
report_id BIGINT NOT NULL REFERENCES lab_reports (id),
code TEXT NOT NULL, -- LOINC code
display TEXT NOT NULL,
value NUMERIC NOT NULL,
unit TEXT NOT NULL,
ref_low NUMERIC,
ref_high NUMERIC,
flag TEXT NOT NULL, -- N, L, H, LL, HH, AA
PRIMARY KEY (report_id, code)
);
The partial index covers only critical results that still need an acknowledgement or an escalation, which keeps the job's query small as the table grows. Observations reference their report revision, so a corrected result never changes the values a clinician already saw on the earlier revision.
Encore is a backend framework with built-in infrastructure for TypeScript and Go, where the database, the versioned bucket, the alerts topic, the cron job and the lab's API key are declared in the service code. When the app deploys to your own AWS account, Encore provisions RDS, S3, SNS and SQS and Secrets Manager with IAM for the service, as the AWS infrastructure docs list. The same code deploys to GCP with Cloud SQL, Cloud Storage, Pub/Sub and Secret Manager.

Run in your terminal to get started locally.
Put the schema above in labs/migrations/1_labs.up.sql. The lab's API key and the pager API key are not stored in the repository, and each environment gets its own values, so a test lab cannot post into production.
// labs/labs.go
package labs
import (
"bytes"
"context"
"crypto/subtle"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
"encore.dev/cron"
"encore.dev/pubsub"
"encore.dev/storage/objects"
"encore.dev/storage/sqldb"
)
// Set per environment with `encore secret set`, never stored in the repo.
var secrets struct {
LabAPIKey string // shared with the lab's integration engine
PagerAPIKey string // paging or secure messaging provider
}
var db = sqldb.NewDatabase("labs", sqldb.DatabaseConfig{Migrations: "./migrations"})
// The messages exactly as the lab sent them, kept for audits and disputes.
var rawMessages = objects.NewBucket("lab-messages", objects.BucketConfig{Versioned: true})
type Observation struct {
Code string `json:"code"` // LOINC, e.g. 2823-3 for serum potassium
Display string `json:"display"`
Value float64 `json:"value"`
Unit string `json:"unit"`
RefLow *float64 `json:"ref_low,omitempty"`
RefHigh *float64 `json:"ref_high,omitempty"`
Flag string `json:"flag"` // N, L, H, LL, HH, AA
}
type ResultMessage struct {
APIKey string `header:"X-Lab-Api-Key" json:"-"`
Lab string `json:"lab"`
Accession string `json:"accession"`
Revision int `json:"revision"`
PatientID string `json:"patient_id"`
ClinicianID string `json:"clinician_id"`
Observations []Observation `json:"observations"`
}
type ReceiveResponse struct {
ReportID int64 `json:"report_id"`
Duplicate bool `json:"duplicate"`
}
// Alert is published for critical results and for escalations.
type Alert struct {
Kind string `json:"kind"` // "critical" or "escalation"
ReportID int64 `json:"report_id"`
ClinicianID string `json:"clinician_id"`
Codes []string `json:"codes"`
}
var Alerts = pubsub.NewTopic[*Alert]("lab-alerts", pubsub.TopicConfig{
DeliveryGuarantee: pubsub.AtLeastOnce,
})
func isCritical(flag string) bool { return flag == "HH" || flag == "LL" || flag == "AA" }
// Receive accepts a result message from the lab's integration engine.
//
//encore:api public method=POST path=/labs/results
func Receive(ctx context.Context, m *ResultMessage) (*ReceiveResponse, error) {
if subtle.ConstantTimeCompare([]byte(m.APIKey), []byte(secrets.LabAPIKey)) != 1 {
return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid lab api key"}
}
if m.Lab == "" || m.Accession == "" || m.Revision < 1 || len(m.Observations) == 0 {
return nil, &errs.Error{Code: errs.InvalidArgument, Message: "lab, accession, revision and observations are required"}
}
var criticalCodes []string
for _, o := range m.Observations {
if isCritical(o.Flag) {
criticalCodes = append(criticalCodes, o.Code)
}
}
// Keep the original before anything else touches it.
key := fmt.Sprintf("%s/%s/r%d.json", m.Lab, m.Accession, m.Revision)
raw, err := json.Marshal(m)
if err != nil {
return nil, err
}
w := rawMessages.Upload(ctx, key, objects.WithUploadAttrs(objects.UploadAttrs{ContentType: "application/json"}))
if _, err := w.Write(raw); err != nil {
w.Abort(err)
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
tx, err := db.Begin(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
var id int64
err = tx.QueryRow(ctx, `
INSERT INTO lab_reports (lab, accession, revision, patient_id, clinician_id, raw_key, critical)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (lab, accession, revision) DO NOTHING
RETURNING id`,
m.Lab, m.Accession, m.Revision, m.PatientID, m.ClinicianID, key, len(criticalCodes) > 0).Scan(&id)
if errors.Is(err, sqldb.ErrNoRows) {
// The lab resent a message we already have. Publish any alert that
// was recorded but not yet delivered, then answer with the stored report.
err = db.QueryRow(ctx, `SELECT id FROM lab_reports WHERE lab = $1 AND accession = $2 AND revision = $3`,
m.Lab, m.Accession, m.Revision).Scan(&id)
if err != nil {
return nil, err
}
if err := publishOutstandingAlert(ctx, id); err != nil {
return nil, err
}
return &ReceiveResponse{ReportID: id, Duplicate: true}, nil
} else if err != nil {
return nil, err
}
for _, o := range m.Observations {
if _, err := tx.Exec(ctx, `
INSERT INTO observations (report_id, code, display, value, unit, ref_low, ref_high, flag)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
id, o.Code, o.Display, o.Value, o.Unit, o.RefLow, o.RefHigh, o.Flag); err != nil {
return nil, err
}
}
if len(criticalCodes) > 0 {
if _, err := tx.Exec(ctx, `
INSERT INTO alert_outbox (report_id, clinician_id, codes)
VALUES ($1, $2, $3)
ON CONFLICT (report_id) DO NOTHING`,
id, m.ClinicianID, strings.Join(criticalCodes, ",")); err != nil {
return nil, err
}
}
if err := tx.Commit(); err != nil {
return nil, err
}
if err := publishOutstandingAlert(ctx, id); err != nil {
return nil, err // outbox row remains; a lab retry or the cron will publish it
}
return &ReceiveResponse{ReportID: id}, nil
}
func publishOutstandingAlert(ctx context.Context, reportID int64) error {
var clinicianID, codes string
err := db.QueryRow(ctx, `
UPDATE alert_outbox
SET claimed_at = now()
WHERE report_id = $1 AND published_at IS NULL
AND (claimed_at IS NULL OR claimed_at < now() - interval '2 minutes')
RETURNING clinician_id, codes`, reportID).Scan(&clinicianID, &codes)
if errors.Is(err, sqldb.ErrNoRows) {
return nil
}
if err != nil {
return err
}
var list []string
if codes != "" {
list = strings.Split(codes, ",")
}
if _, err := Alerts.Publish(ctx, &Alert{Kind: "critical", ReportID: reportID, ClinicianID: clinicianID, Codes: list}); err != nil {
return err
}
_, err = db.Exec(ctx, `
UPDATE alert_outbox SET published_at = now()
WHERE report_id = $1 AND published_at IS NULL`, reportID)
return err
}
// AckCritical records that the ordering clinician has seen a critical result.
//
//encore:api auth method=POST path=/reports/:id/ack
func AckCritical(ctx context.Context, id int64) error {
uid, _ := auth.UserID()
res, err := db.Exec(ctx, `
UPDATE lab_reports SET critical_acked_at = now(), critical_acked_by = $2
WHERE id = $1 AND critical AND clinician_id = $2 AND critical_acked_at IS NULL`,
id, string(uid))
if err != nil {
return err
}
if res.RowsAffected() == 0 {
return &errs.Error{Code: errs.NotFound, Message: "no unacknowledged critical result for you"}
}
return nil
}
// Release makes a reviewed report visible in the patient portal.
//
//encore:api auth method=POST path=/reports/:id/release
func Release(ctx context.Context, id int64) error {
uid, _ := auth.UserID()
res, err := db.Exec(ctx, `
UPDATE lab_reports SET released_at = now(), released_by = $2
WHERE id = $1 AND clinician_id = $2 AND released_at IS NULL
AND (NOT critical OR critical_acked_at IS NOT NULL)`,
id, string(uid))
if err != nil {
return err
}
if res.RowsAffected() == 0 {
return &errs.Error{Code: errs.FailedPrecondition, Message: "report not found, already released or critical result not acknowledged"}
}
return nil
}
const ackWindow = 30 * time.Minute
// EscalateUnacked publishes an escalation for critical results nobody
// acknowledged within the window, once per report.
//
//encore:api private
func EscalateUnacked(ctx context.Context) error {
pending, err := db.Query(ctx, `SELECT report_id FROM alert_outbox WHERE published_at IS NULL`)
if err != nil {
return err
}
defer pending.Close()
for pending.Next() {
var id int64
if err := pending.Scan(&id); err != nil {
return err
}
if err := publishOutstandingAlert(ctx, id); err != nil {
return err
}
}
if err := pending.Err(); err != nil {
return err
}
rows, err := db.Query(ctx, `
UPDATE lab_reports SET escalated_at = now()
WHERE critical AND critical_acked_at IS NULL AND escalated_at IS NULL
AND received_at < now() - make_interval(mins => $1::int)
RETURNING id, clinician_id`, int(ackWindow.Minutes()))
if err != nil {
return err
}
defer rows.Close()
var alerts []*Alert
for rows.Next() {
a := &Alert{Kind: "escalation"}
if err := rows.Scan(&a.ReportID, &a.ClinicianID); err != nil {
return err
}
alerts = append(alerts, a)
}
if err := rows.Err(); err != nil {
return err
}
for _, a := range alerts {
if _, err := Alerts.Publish(ctx, a); err != nil {
return err
}
}
return nil
}
var _ = cron.NewJob("escalate-critical-results", cron.JobConfig{
Title: "Escalate unacknowledged critical lab results",
Every: 5 * cron.Minute,
Endpoint: EscalateUnacked,
})
var pager = &http.Client{Timeout: 5 * time.Second}
func pageClinician(ctx context.Context, a *Alert) error {
body, err := json.Marshal(map[string]string{
"kind": a.Kind,
"report_id": fmt.Sprintf("%d", a.ReportID),
"clinician": a.ClinicianID,
})
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://pager.example.com/v1/page", bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+secrets.PagerAPIKey)
req.Header.Set("Idempotency-Key", fmt.Sprintf("alert-%d-%s", a.ReportID, a.Kind))
req.Header.Set("Content-Type", "application/json")
resp, err := pager.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return fmt.Errorf("pager returned %d", resp.StatusCode)
}
return nil
}
var _ = pubsub.NewSubscription(Alerts, "page-clinician", pubsub.SubscriptionConfig[*Alert]{
Handler: func(ctx context.Context, a *Alert) error {
if _, err := db.Exec(ctx, `
INSERT INTO sent_alerts (report_id, kind)
VALUES ($1, $2)
ON CONFLICT DO NOTHING`, a.ReportID, a.Kind); err != nil {
return err
}
var claimed int64
err := db.QueryRow(ctx, `
UPDATE sent_alerts
SET claimed_at = now()
WHERE report_id = $1 AND kind = $2 AND delivered_at IS NULL
AND (claimed_at IS NULL OR claimed_at < now() - interval '2 minutes')
RETURNING report_id`, a.ReportID, a.Kind).Scan(&claimed)
if errors.Is(err, sqldb.ErrNoRows) {
var deliveredAt *time.Time
if err := db.QueryRow(ctx, `
SELECT delivered_at FROM sent_alerts
WHERE report_id = $1 AND kind = $2`, a.ReportID, a.Kind).Scan(&deliveredAt); err != nil {
return err
}
if deliveredAt != nil {
return nil
}
// The provider idempotency key makes retrying this alert safe.
} else if err != nil {
return err
}
// For an escalation, page the on-call clinician as well as the ordering one.
if err := pageClinician(ctx, a); err != nil {
return err
}
_, err = db.Exec(ctx, `
UPDATE sent_alerts SET delivered_at = now()
WHERE report_id = $1 AND kind = $2 AND delivered_at IS NULL`, a.ReportID, a.Kind)
return err
},
})
Critical alerts are written to alert_outbox in the same transaction as the report. publishOutstandingAlert claims a pending row with an atomic UPDATE ... RETURNING and a two-minute lease, then marks it published only after Alerts.Publish succeeds, so a lab retry and the cron job cannot both publish the same alert. The page-clinician subscriber records a sent_alerts row, claims it with an atomic UPDATE ... RETURNING and a two-minute lease, pages with Idempotency-Key: alert-{reportID}-{kind} on the provider request, and sets delivered_at only after the provider acknowledges the page. When a redelivery arrives while a lease is still held, the handler pages again with the same key rather than acknowledging the message, so a crash after the claim cannot drop the alert, and a completed row is left untouched. A lab retry of a stored report still publishes a pending row, and EscalateUnacked flushes any leftover pending alerts before it looks for unacknowledged results. AckCritical and Release use the auth access level, so they need an auth handler that maps your clinician sessions to user IDs, and both only touch reports where the caller is the ordering clinician. The X-Lab-Api-Key header is parsed into the request struct and left out of the stored JSON with json:"-". The UPDATE ... RETURNING in EscalateUnacked marks and returns each report in one statement, so a report is escalated once even when two runs overlap.
The database docs cover migrations and transactions, the object storage docs cover versioned buckets, and the cron job docs cover schedules. Cron jobs do not run locally or in preview environments, so during development you call EscalateUnacked from the local development dashboard. Once deployed, the Encore platform calls it every five minutes.
Connect your AWS account as described in Connect your cloud account, create an environment and run git push encore. When a coding agent later adds a feature, such as notifying the clinician about corrections to released reports, the change is a new alert kind and a query in this service, and it reaches production through the same review and deploy as any other pull request.
It receives result messages from a lab or its integration engine, stores each report and its observations, routes critical values to the ordering clinician right away, and controls when a report becomes visible to the patient. It also keeps the original messages, so the record can be checked against what the lab sent.
Use the interpretation flag the lab sends with each observation. In the HL7 FHIR observation interpretation codes, HH means critical high, LL critical low and AA critical abnormal, and the lab sets them against its own critical limits, so the backend routes those flags instead of recomputing thresholds.
Store every revision of a report as its own row keyed by lab, accession number and revision, and never overwrite the earlier one. Show the latest revision, keep the history, and notify the clinician when a correction arrives for a report that was already released.
A scheduled job looks for critical results without an acknowledgement after a set window, marks them as escalated in the same UPDATE so each one escalates once, and pages the on-call clinician as well as the ordering one. The window and escalation path come from your clinical leadership, not from code defaults.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.