Skip to content

fix: async newsletter emails, migration locking, webhook signature enforcement, DLQ metric - #829

Merged
hman38705 merged 4 commits into
solutions-plug:mainfrom
Fidelis900:main
May 30, 2026
Merged

fix: async newsletter emails, migration locking, webhook signature enforcement, DLQ metric#829
hman38705 merged 4 commits into
solutions-plug:mainfrom
Fidelis900:main

Conversation

@Fidelis900

Copy link
Copy Markdown
Contributor

Summary

This PR resolves four backend reliability and security issues across the email and migration subsystems.


fix(#677) — Newsletter confirmation email is now non-blocking

File: services/api/src/handlers.rs

The newsletter_subscribe handler was already enqueuing confirmation emails via email/queue.rs, but still returned 200 OK — implying the operation had completed synchronously. Changed the response to 202 Accepted so the status code accurately reflects that the email has been accepted for asynchronous processing, not sent inline. This also aligns the endpoint with REST semantics for async operations.


fix(#676) — Migrations are now serialized with a PostgreSQL advisory lock

File: services/api/src/migrations.rs

In a horizontally scaled deployment, multiple instances starting simultaneously could each call MigrationRunner::run() concurrently, resulting in duplicate or conflicting migration attempts.

run() now:

  1. Acquires a session-level advisory lock via pg_try_advisory_lock on a stable key (0x707265646963_7471) before executing any migrations.
  2. If the lock is already held by another instance, aborts immediately with a clear error — causing that instance's startup to fail rather than racing.
  3. Always calls pg_advisory_unlock before the connection is returned to the pool (both on success and failure), preventing the lock from leaking across pool-reused connections.

The migration logic itself is extracted into a private run_inner() to keep the locking and execution concerns separate.


fix(#679) — Webhook HMAC signature verification is now enforced by default

File: services/api/src/security.rs

sendgrid_webhook_middleware determined whether to run in development mode (bypassing signature verification) via:

.unwrap_or(true) // default to dev if not set

If the ENVIRONMENT environment variable was absent — which is common in staging or misconfigured production deployments — the middleware silently skipped HMAC verification, allowing any party to forge webhook events. Changed the default to false so that signature verification is enforced unless ENVIRONMENT=development is explicitly set.


fix(#678) — DLQ size exposed as a Prometheus gauge

Files: services/api/src/metrics.rs, services/api/src/handlers.rs

The dead-letter queue (Redis key email:dead_letter) had no observability. Added an email_dlq_size IntGauge to the Metrics registry. The gauge is updated each time the GET /api/v1/email/queue/stats endpoint is called, reflecting the current DLQ cardinality from Redis.

The existing list_dead_letter (GET /api/v1/email/dead-letter) and requeue_dead_letter (POST /api/v1/email/dead-letter/:id/requeue) admin endpoints satisfy the inspect-and-replay acceptance criterion — no new endpoints were needed.


Closes

Closes #677
Closes #676
Closes #679
Closes #678

🤖 Generated with Claude Code

Fidelis900 and others added 4 commits May 30, 2026 13:12
…er confirmation email

Newsletter subscription emails are already enqueued via email/queue.rs.
Changed the response status from 200 OK to 202 Accepted to accurately
reflect that the request was accepted for async processing, not completed
inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SQL advisory lock

MigrationRunner::run() now acquires a session-level advisory lock via
pg_try_advisory_lock before executing any migrations. If another instance
already holds the lock the call aborts with a clear error, causing startup
to fail rather than running duplicate migrations. The lock is always
released (even on failure) before the connection returns to the pool to
avoid leaking it across pool reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ONMENT is unset

The sendgrid_webhook_middleware defaulted is_dev to true when the
ENVIRONMENT variable was absent, which silently bypassed signature
verification for any deployment that did not explicitly set the variable.
Changed the default to false so that signature verification is enforced
unless ENVIRONMENT=development is explicitly configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s gauge

Added email_dlq_size IntGauge to the Metrics registry. The gauge is
updated each time the email_queue_stats endpoint is called, reflecting
the current Redis DLQ (email:dead_letter) cardinality. The existing
list_dead_letter and requeue_dead_letter methods plus their admin HTTP
endpoints satisfy the inspect-and-replay acceptance criterion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented May 30, 2026

Copy link
Copy Markdown

@Fidelis900 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@hman38705
hman38705 merged commit 99d5f47 into solutions-plug:main May 30, 2026
12 of 39 checks passed
hman38705 added a commit that referenced this pull request May 30, 2026
…pool metrics from PRs #829 and #830

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants