Testing Guide
Step-by-step instructions to verify match result email notifications locally and with a real Resend key.
supabase start — local Supabase runningsupabase db reset — fresh seed data appliednpm run dev (in apps/admin) — admin app on http://localhost:3000supabase functions serve send-match-email --env-file /tmp/edge-fn-env — Edge Function runningcd /workspaces/dev1
supabase start
supabase db reset
cd apps/admin && npm run dev &
cat > /tmp/edge-fn-env <<'EOF'
RESEND_API_KEY=re_test_XXXXXXXX # Get from resend.com (free tier)
WEB_APP_URL=http://localhost:3002
EOF
supabase functions serve send-match-email --env-file /tmp/edge-fn-env
curl -s -X POST "http://127.0.0.1:54321/rest/v1/api_keys" \
-H "Content-Type: application/json" \
-H "apikey: $(supabase status | grep 'Secret' | awk '{print $NF}')" \
-H "Authorization: Bearer $(supabase status | grep 'Secret' | awk '{print $NF}')" \
-H "Prefer: return=minimal" \
-d '{
"facility_id": "00000000-0000-0000-0000-000000000001",
"key_hash": "50f535341eba98533273917efc99b525dbab3524752d77593446f99666eac6a3",
"key_prefix": "sk_live_test",
"name": "Manual Test Key",
"scopes": ["*"],
"is_active": true
}'
Call the Edge Function directly with curl to verify data fetching, recipient logic, and email construction for each event type.
# These are printed by "supabase status"
ANON_KEY="sb_publishable_..."
SERVICE_KEY="sb_secret_..."
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{
"event_type": "result_submitted",
"result_id": "eeeeeeee-0000-0000-0000-000000000001",
"actor_user_id": "aaaaaaaa-0000-0000-0000-000000000001"
}'
Expected: 3 recipients (Maria, Priya, Tom) — Carlos excluded as submitter.
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{
"event_type": "result_confirmed",
"result_id": "eeeeeeee-0000-0000-0000-000000000001",
"actor_user_id": "aaaaaaaa-0000-0000-0000-000000000002"
}'
Expected: 4 recipients (Carlos, Maria, Priya, Tom) — no exclusion.
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{
"event_type": "result_auto_confirmed",
"result_id": "eeeeeeee-0000-0000-0000-000000000001"
}'
Expected: 4 recipients — no actor to exclude.
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{
"event_type": "result_disputed",
"result_id": "eeeeeeee-0000-0000-0000-000000000001",
"actor_user_id": "aaaaaaaa-0000-0000-0000-000000000002"
}'
Expected: 3 recipients (Carlos, Priya, Tom) — Maria excluded as disputer.
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{
"event_type": "result_admin_resolved",
"result_id": "eeeeeeee-0000-0000-0000-000000000001"
}'
Expected: 4 recipients — no exclusion.Expected: Each call returns { sent: N, results: [...] }. With re_test_ key: sent=0 and status='failed' (expected — test keys don't send). With real key: sent=N and status='sent', and emails arrive in recipients' inboxes.
Verify invalid payloads are rejected with proper error messages.
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{"event_type": "result_submitted"}'
Expected: 400 — "event_type and result_id are required"
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{"event_type": "invalid", "result_id": "test"}'
Expected: 400 — "Unknown event_type: invalid"
curl -s -X POST "http://127.0.0.1:54321/functions/v1/send-match-email" \
-H "Content-Type: application/json" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $SERVICE_KEY" \
-d '{"event_type": "result_submitted", "result_id": "00000000-0000-0000-0000-000000000000"}'
Expected: 404 — "Result not found"Expected: Proper HTTP status codes and error messages for each case.
Test the full flow through the admin API routes to verify the Edge Function is called as fire-and-forget.
MARIA_TOKEN=$(curl -s -X POST "http://127.0.0.1:54321/auth/v1/token?grant_type=password" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "maria@demo.local", "password": "demo-password-123"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "http://localhost:3000/api/v1/matches/cccccccc-0000-0000-0000-000000000009/results" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MARIA_TOKEN" \
-H "X-API-Key: sk_live_testkey1234567890abcdefgh" \
-d '{
"team_a_player_ids": ["aaaaaaaa-0000-0000-0000-000000000001", "aaaaaaaa-0000-0000-0000-000000000002"],
"team_b_player_ids": ["aaaaaaaa-0000-0000-0000-000000000005", "aaaaaaaa-0000-0000-0000-000000000007"],
"score": [{"team_a": 6, "team_b": 4}, {"team_a": 6, "team_b": 3}],
"winner": "team_a"
}'
Expected: 201 with result object. Check Edge Function logs for "[SEND-MATCH-EMAIL] result_submitted" log line.
Verify: API response returns instantly (not blocked by email). Edge Function log shows correct recipient count.
CARLOS_TOKEN=$(curl -s -X POST "http://127.0.0.1:54321/auth/v1/token?grant_type=password" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "carlos@demo.local", "password": "demo-password-123"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "http://localhost:3000/api/v1/matches/cccccccc-0000-0000-0000-000000000009/results/confirm" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CARLOS_TOKEN" \
-H "X-API-Key: sk_live_testkey1234567890abcdefgh"
Expected: { "confirmed": true, "result_id": "..." }. Edge Function log shows "result_confirmed".
# Submit for match 010 first
curl -s -X POST "http://localhost:3000/api/v1/matches/cccccccc-0000-0000-0000-000000000010/results" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MARIA_TOKEN" \
-H "X-API-Key: sk_live_testkey1234567890abcdefgh" \
-d '{
"team_a_player_ids": ["aaaaaaaa-0000-0000-0000-000000000002"],
"team_b_player_ids": ["aaaaaaaa-0000-0000-0000-000000000003"],
"score": [{"team_a": 6, "team_b": 4}, {"team_a": 6, "team_b": 3}],
"winner": "team_a"
}'
# Now dispute as Jake
JAKE_TOKEN=$(curl -s -X POST "http://127.0.0.1:54321/auth/v1/token?grant_type=password" \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jake@demo.local", "password": "demo-password-123"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "http://localhost:3000/api/v1/matches/cccccccc-0000-0000-0000-000000000010/results/dispute" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JAKE_TOKEN" \
-H "X-API-Key: sk_live_testkey1234567890abcdefgh" \
-d '{
"proposed_score": [{"team_a": 4, "team_b": 6}, {"team_a": 3, "team_b": 6}],
"proposed_winner": "team_b",
"reason": "Score was entered incorrectly"
}'
Expected: 201 with dispute object. Edge Function log shows "result_disputed".Expected: All API responses return the same shape as before (no regressions). Edge Function logs show email attempts for each event.
Verify admin resolve triggers result_admin_resolved email.
curl -s -X POST "http://localhost:3000/api/admin/matches/cccccccc-0000-0000-0000-000000000016/results/resolve" \
-H "Content-Type: application/json" \
-d '{
"score": [{"team_a": 6, "team_b": 4}, {"team_a": 6, "team_b": 3}],
"winner": "team_a"
}'
Expected: { "resolved": true, "result_id": "..." }. Edge Function log shows "result_admin_resolved".Expected: Resolve returns success. Edge Function log shows all 4 participants notified (no exclusion for admin resolve).
Verify the PostgreSQL auto-confirm function calls the Edge Function via pg_net.
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
ALTER DATABASE postgres SET app.settings.supabase_url = 'http://kong:8000';
ALTER DATABASE postgres SET app.settings.service_role_key = 'sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz';
ALTER DATABASE postgres SET app.settings.anon_key = 'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH';
"
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
UPDATE match_results
SET confirmation_deadline = now() - interval '1 hour'
WHERE id = 'eeeeeeee-0000-0000-0000-000000000002';
"
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
SELECT auto_confirm_match_results();
"
Note: This may fail with an Elo numeric overflow error (pre-existing issue — player_ratings.level is numeric(4,2) which can't hold 1500). This is NOT related to v0.15.1.
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
SELECT net.http_post(
url := 'http://kong:8000/functions/v1/send-match-email',
body := '{"event_type": "result_auto_confirmed", "result_id": "eeeeeeee-0000-0000-0000-000000000001"}'::jsonb,
headers := '{"Content-Type": "application/json", "apikey": "sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH", "Authorization": "Bearer sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz"}'::jsonb
);
"
sleep 2
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
SELECT id, status_code, content FROM net._http_response ORDER BY id DESC LIMIT 1;
"
Expected: status_code = 200, content shows { "sent": N, "results": [...] }Expected: pg_net successfully reaches the Edge Function from within PostgreSQL. Response logged in net._http_response with status 200.
Verify actual emails arrive in inboxes using a real Resend API key.
1. Sign up at https://resend.com (free tier = 100 emails/day)
2. Go to API Keys and create a new key
3. You'll get a key like re_abc123...
cat > /tmp/edge-fn-env <<'EOF'
RESEND_API_KEY=re_YOUR_REAL_KEY_HERE
WEB_APP_URL=http://localhost:3002
EOF
# Restart the Edge Function
supabase functions serve send-match-email --env-file /tmp/edge-fn-env
Resend free tier only allows sending from onboarding@resend.dev unless you verify a domain.
You'll need to temporarily change the "from" in the Edge Function, OR verify your domain in Resend.
You can insert a test profile with your real email and run a test:
# Update one of the demo users' email to yours
docker exec -i $(docker ps --filter "name=supabase_db" --format "{{.Names}}") psql -U postgres -c "
UPDATE customers SET email = 'your-real-email@example.com'
WHERE id = 'aaaaaaaa-0000-0000-0000-000000000002';
"
Then run test T1b (result_confirmed) — you should receive the email.
- Subject line matches the event type
- Facility name in header ("Sunset Padel Club")
- "Hi [FirstName]," greeting
- Score displayed correctly (e.g., "6-3, 6-4")
- Winner shows "Team A" or "Team B"
- Sport, court, and date are correct
- "View Match" button links to the correct URL
- Footer says "Sent by [Facility] via Setpoint"
Expected: Real emails arrive in inbox with correct formatting, branding, and CTA link.
| Match ID | Sport | Status | Result Status | Result ID |
|---|---|---|---|---|
| ...0014 | padel | completed | confirmed | eeee...0001 |
| ...0015 | pickleball | completed | pending_review | eeee...0002 |
| ...0016 | padel | completed | admin_review | eeee...0003 |
| Match ID | Sport | Status | Players |
|---|---|---|---|
| ...0009 | padel | full | Carlos, Maria, Alex, Tom |
| ...0010 | tennis | full | Maria, Jake |
| ...0011 | padel | full | (check match_players) |
| User ID | Name | Password | |
|---|---|---|---|
| ...0001 | Carlos Ruiz | carlos@demo.local | demo-password-123 |
| ...0002 | Maria Santos | maria@demo.local | demo-password-123 |
| ...0003 | Jake Thompson | jake@demo.local | demo-password-123 |
| ...0006 | Priya Patel | priya@demo.local | demo-password-123 |
| ...0007 | Tom Wilson | tom@demo.local | demo-password-123 |
Key: sk_live_testkey1234567890abcdefgh
(must be seeded — see Setup section above)