Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EdgarJr30/proyecto-de-grado-cms/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The send-push-from-outbox Edge Function is a Deno-based worker that processes the notification_outbox table and delivers Web Push notifications to subscribed browsers.
Location: supabase/functions/send-push-from-outbox/index.ts

Architecture

The Edge Function implements a claim-and-process pattern for concurrency-safe delivery:
1

Claim Pending Rows

Uses claim_notification_outbox_batch RPC with SKIP LOCKED to atomically claim rows without conflicts
2

Fetch Subscriptions

Loads active push_subscriptions for each recipient from the database
3

Send Push Payload

Uses web-push library to send notifications to all endpoints with VAPID authentication
4

Update Status

Marks rows as sent (success) or schedules retry with exponential backoff (failure)
5

Clean Expired Subscriptions

Removes subscriptions that return 404/410 status codes (unsubscribed/expired)

Environment Variables

Required Secrets

Set these via npx supabase secrets set:
VariableDescriptionExample
SUPABASE_URLYour Supabase project URLhttps://abc123.supabase.co
SUPABASE_SERVICE_ROLE_KEYService role key (never anon key)eyJhbGc...
VAPID_PUBLIC_KEYVAPID public key from generation stepBKxW8z...
VAPID_PRIVATE_KEYVAPID private key (keep secret)y3dK...

Optional Configuration

VariableDescriptionDefault
VAPID_SUBJECTContact email or URLmailto:notifications@example.com
PUSH_OUTBOX_CRON_SECRETShared secret for webhook authentication(none)
PUSH_OUTBOX_MAX_ATTEMPTSMax retries before marking as error8
PUSH_OUTBOX_BACKOFF_BASE_SECONDSInitial retry delay15
PUSH_OUTBOX_BACKOFF_MAX_SECONDSMaximum retry delay900 (15 min)
PUSH_OUTBOX_PROCESSING_LEASE_SECONDSHow long to hold a processing lock120 (2 min)
PUSH_OUTBOX_MAX_PARALLEL_SENDSConcurrent push sends per batch8
The PUSH_OUTBOX_CRON_SECRET is required if you enable webhook-based invocation to prevent unauthorized calls.

Deployment

Step 1: Set Secrets

npx supabase secrets set \
  SUPABASE_URL="https://your-project.supabase.co" \
  SUPABASE_SERVICE_ROLE_KEY="eyJhbGc..." \
  VAPID_PUBLIC_KEY="BKxW8z..." \
  VAPID_PRIVATE_KEY="y3dK..." \
  VAPID_SUBJECT="mailto:admin@yourdomain.com" \
  PUSH_OUTBOX_CRON_SECRET="$(openssl rand -base64 32)" \
  --project-ref <your-project-ref>

Step 2: Deploy Function

npx supabase functions deploy send-push-from-outbox \
  --project-ref <your-project-ref>
Expected output:
Deploying function send-push-from-outbox...
Function URL: https://<project-ref>.functions.supabase.co/send-push-from-outbox

Step 3: Verify Deployment

Test with a manual invocation:
curl -X POST "https://<project-ref>.functions.supabase.co/send-push-from-outbox?limit=10" \
  -H "Authorization: Bearer <SUPABASE_SERVICE_ROLE_KEY>" \
  -H "apikey: <SUPABASE_SERVICE_ROLE_KEY>" \
  -H "x-cron-secret: <PUSH_OUTBOX_CRON_SECRET>" \
  -H "Content-Type: application/json"
Expected response:
{
  "ok": true,
  "mode": "batch",
  "outbox_id": null,
  "claimed": 3,
  "processed": 3,
  "sent": 3,
  "failed": 0
}

Invocation Modes

Mode 1: Batch Processing

Process multiple pending rows (default):
POST /send-push-from-outbox?limit=100
  • Claims up to limit pending rows (default 100, max 250)
  • Processes in parallel with MAX_PARALLEL_SENDS workers
  • Returns aggregate stats

Mode 2: Single Row

Process a specific outbox row by ID:
POST /send-push-from-outbox?outbox_id=<uuid>&limit=1
  • Useful for webhook-triggered immediate delivery
  • Bypasses normal queue ordering

Retry Logic

The function implements exponential backoff with configurable bounds:
// supabase/functions/send-push-from-outbox/index.ts:120
function computeBackoff(attempts: number) {
  const baseSeconds = normalizeInt(BACKOFF_BASE_SECONDS, 1, 300, 15);
  const maxSeconds = normalizeInt(BACKOFF_MAX_SECONDS, baseSeconds, 3600, 900);
  const exponent = Math.max(0, Math.min(attempts - 1, 10));
  const delaySeconds = Math.min(baseSeconds * 2 ** exponent, maxSeconds);
  return new Date(Date.now() + delaySeconds * 1000).toISOString();
}
Example retry schedule (with defaults):
AttemptDelay
115s
230s
31m
42m
54m
68m
7+15m (capped)
After MAX_ATTEMPTS (default 8), rows are marked as error and stop retrying.

Subscription Cleanup

The function automatically removes expired subscriptions:
// supabase/functions/send-push-from-outbox/index.ts:337
if (status === 404 || status === 410) {
  expiredEndpoints.push(subscription.endpoint);
}
  • 410 Gone: User unsubscribed
  • 404 Not Found: Subscription no longer valid
Removed subscriptions won’t be retried on future deliveries.

Webhook Configuration

Option A: Database Trigger (pg_net)

Recommended for immediate delivery without external dependencies.
Configure database settings (requires ALTER DATABASE privilege):
ALTER DATABASE postgres 
SET app.settings.push_outbox_worker_url = 'https://<project-ref>.functions.supabase.co/send-push-from-outbox';

ALTER DATABASE postgres 
SET app.settings.push_outbox_service_jwt = '<SUPABASE_SERVICE_ROLE_KEY>';

ALTER DATABASE postgres 
SET app.settings.push_outbox_cron_secret = '<PUSH_OUTBOX_CRON_SECRET>';
The trg_enqueue_push_outbox_dispatch trigger (line 1421 in 16_notifications.sql) will invoke the function via pg_net.http_post immediately when rows are inserted.
If you lack ALTER DATABASE privileges, use Option B instead.

Option B: Database Webhook

Create a webhook in Supabase Dashboard:
  1. Navigate to DatabaseWebhooksCreate a new hook
  2. Configure:
    • Table: public.notification_outbox
    • Events: INSERT
    • Type: HTTP Request
    • Method: POST
    • URL: https://<project-ref>.functions.supabase.co/send-push-from-outbox?limit=1
  3. Add headers:
    Authorization: Bearer <SUPABASE_SERVICE_ROLE_KEY>
    apikey: <SUPABASE_SERVICE_ROLE_KEY>
    x-cron-secret: <PUSH_OUTBOX_CRON_SECRET>
    Content-Type: application/json
    

Option C: Cron Fallback

Always recommended as a safety net, even with immediate triggers.
Set up a cron job to process stale/stuck rows every minute:
# crontab example
* * * * * curl -sS -X POST \
  "https://<project-ref>.functions.supabase.co/send-push-from-outbox?limit=100" \
  -H "Authorization: Bearer <SUPABASE_SERVICE_ROLE_KEY>" \
  -H "apikey: <SUPABASE_SERVICE_ROLE_KEY>" \
  -H "x-cron-secret: <PUSH_OUTBOX_CRON_SECRET>" \
  -H "Content-Type: application/json" \
  >> /var/log/push-outbox.log 2>&1
Or use a service like Uptime Robot, Cron-job.org, or GitHub Actions.

Monitoring

Check Outbox Health

-- Status distribution
SELECT status, COUNT(*) 
FROM notification_outbox 
GROUP BY status;

-- Recent errors
SELECT id, attempts, last_error, next_attempt_at, created_at
FROM notification_outbox
WHERE status = 'error'
ORDER BY created_at DESC
LIMIT 10;

-- Stuck in processing (potential worker crashes)
SELECT id, attempts, next_attempt_at, created_at
FROM notification_outbox
WHERE status = 'processing'
  AND next_attempt_at < NOW() - INTERVAL '5 minutes'
ORDER BY created_at DESC;

Function Logs

View Edge Function logs in Supabase Dashboard:
  1. Navigate to Edge Functionssend-push-from-outbox
  2. Click Logs tab
  3. Filter by status >= 400 for errors
Common errors:
  • Unauthorized: Missing or invalid x-cron-secret
  • Invalid outbox_id format: Malformed UUID in query param
  • push_send_failed: All subscriptions failed (check endpoint validity)

Performance Tuning

Adjust Parallelism

Increase for high-volume deployments:
npx supabase secrets set PUSH_OUTBOX_MAX_PARALLEL_SENDS=16 \
  --project-ref <your-project-ref>

Batch Size

Increase the cron limit parameter for larger batches:
# Process up to 250 rows per invocation
curl ... "?limit=250"
Be mindful of Edge Function execution time limits (varies by Supabase plan).

Processing Lease

Reduce if workers are terminating prematurely:
npx supabase secrets set PUSH_OUTBOX_PROCESSING_LEASE_SECONDS=60 \
  --project-ref <your-project-ref>

Troubleshooting

No Notifications Sent

  1. Check outbox has pending rows:
    SELECT * FROM notification_outbox WHERE status = 'pending' LIMIT 5;
    
  2. Verify function secrets are set:
    npx supabase secrets list --project-ref <your-project-ref>
    
  3. Test manual invocation (see Verify Deployment above)
  4. Check user has active subscription:
    SELECT * FROM push_subscriptions WHERE user_id = '<user-uuid>';
    

Rows Stuck in Processing

If rows remain in processing status:
  1. Check if processing lease expired:
    SELECT id, next_attempt_at FROM notification_outbox 
    WHERE status = 'processing' AND next_attempt_at < NOW();
    
  2. The next cron/claim call will automatically retry expired leases
  3. Or manually reset:
    UPDATE notification_outbox 
    SET status = 'pending', next_attempt_at = NOW()
    WHERE status = 'processing' AND next_attempt_at < NOW() - INTERVAL '10 minutes';
    

High Error Rate

  1. Check subscription validity:
    SELECT user_id, endpoint, last_seen_at 
    FROM push_subscriptions 
    WHERE last_seen_at < NOW() - INTERVAL '30 days';
    
  2. Review error messages:
    SELECT last_error, COUNT(*) 
    FROM notification_outbox 
    WHERE status = 'error' 
    GROUP BY last_error;
    
  3. Common causes:
    • Expired VAPID keys (regenerate and redeploy)
    • User revoked notification permission
    • Browser/OS blocking push

Next Steps

Configure PWA

Set up service worker registration and subscription management in the frontend

Build docs developers (and LLMs) love