PhoenixKit.Modules.Emails.SQSPollingManager (phoenix_kit_emails v0.4.0)

Copy Markdown View Source

Manager module for SQS polling via Oban jobs.

This module provides a unified API for managing SQS polling that can be enabled/disabled dynamically without application restart.

Features

  • Enable/Disable Polling: Start or stop polling without restart
  • Manual Triggering: Force immediate polling when needed
  • Status Monitoring: Get current polling status and job information
  • Settings Integration: Automatically uses PhoenixKit Settings
  • Interval Control: Dynamically adjust polling frequency

Architecture

Instead of using a GenServer, this manager uses Oban jobs for polling:

  • Each job polls SQS once and schedules the next job
  • Jobs check settings before executing (dynamic control)
  • No need to restart GenServer when settings change

Usage

# Enable polling
iex> PhoenixKit.Modules.Emails.SQSPollingManager.enable_polling()
{:ok, %Oban.Job{}}

# Disable polling
iex> PhoenixKit.Modules.Emails.SQSPollingManager.disable_polling()
:ok

# Check status
iex> PhoenixKit.Modules.Emails.SQSPollingManager.status()
%{
  enabled: true,
  interval_ms: 5000,
  pending_jobs: 1,
  last_run: ~U[2025-09-20 15:30:45Z],
  queue_url: "https://sqs.eu-north-1.amazonaws.com/..."
}

# Trigger immediate poll
iex> PhoenixKit.Modules.Emails.SQSPollingManager.poll_now()
{:ok, %Oban.Job{}}

# Change polling interval
iex> PhoenixKit.Modules.Emails.SQSPollingManager.set_polling_interval(3000)
{:ok, %Setting{}}

Integration

This manager is the single control surface for SQS polling: it drives the Oban SQSPollingJob. enable_polling/0 and disable_polling/0 back the admin UI toggle. poll_now/0 and set_polling_interval/1 are part of the public consumer API (no admin-UI caller today); they are safe to call directly from host applications.

Summary

Functions

Disables SQS polling by updating the configuration.

Enables SQS polling by setting the configuration and starting the first job.

Triggers an immediate polling job.

Sets the polling interval in milliseconds.

Returns the current status of SQS polling.

Functions

disable_polling()

Disables SQS polling by updating the configuration.

Only sqs_polling_enabled is cleared. email_ses_events — which enable_polling/0 turns on — is deliberately left alone, and the asymmetry is the point: enabling asserts "SES event tracking is a thing on this install", which stays true while polling is paused, and the same flag also gates the SNS webhook path (Emails.Web.WebhookController), which has nothing to do with SQS polling. Clearing it here would silently switch off webhook ingestion from a button labelled "stop polling". It keeps its own control in the Email Tracking page (Web.EmailTracking, not the settings section of the same name) for an operator who really does mean "no SES events at all". See EventTracker's moduledoc, which spells out why an eligibility flag otherwise must not move with an operator toggle.

No explicit job cancellation: SQSPollingJob.perform/1 checks should_poll?/0 before doing any work AND before self-scheduling its next cycle (see that module). At most one already-queued job fires once more, sees polling disabled, does nothing, and does not re-schedule — the chain dies on its own within one cycle. That one harmless no-op run is the accepted cost of not doing a manual DELETE here.

Returns

  • :ok - Successfully disabled

Examples

iex> PhoenixKit.Modules.Emails.SQSPollingManager.disable_polling()
:ok

enable_polling()

Enables SQS polling by setting the configuration and starting the first job.

Three writes — email_ses_events, sqs_polling_enabled and the first Oban job — land in ONE transaction. They used to run as three bare steps chained by with, so a failure at step two or three left email_ses_events flipped on by a click that reported an error: the operator saw "failed to enable", the install silently gained an eligibility flag it never had, and nothing on the page said so. Settings.update_settings_batch/1 is the obvious tool and the wrong one here — it writes key/value only, so on an install where these rows do not exist yet it would create them without the email_system module tag the settings page groups by.

Cache invalidation cuts both ways here. On a rollback it stays valid: the writers only CLEAR entries, so the next read comes from the rolled-back row rather than a stale cached one. On success it is not enough on its own — the writers clear inside the transaction, before the commit is visible, so a reader that misses the cache in that window would cache the pre-commit value and keep it for the cache's whole TTL. Both keys are therefore invalidated again after the commit.

Returns

  • {:ok, job} - Successfully enabled and started first job
  • {:error, reason} - Nothing was written; both settings keep their prior values

Examples

iex> PhoenixKit.Modules.Emails.SQSPollingManager.enable_polling()
{:ok, %Oban.Job{id: 1, queue: "sqs_polling"}}

poll_now()

Triggers an immediate polling job.

This creates a new job that will execute as soon as possible, regardless of the normal polling schedule.

The job carries args: %{"forced" => true}, which SQSPollingJob.perform/1 honours by bypassing the sqs_polling_enabled toggle for that single cycle (it still respects the system switch, the SES-events switch, and the sender-aware gate). Without it a manual poll while the toggle is off would insert a job that runs, sees polling disabled, and silently does nothing. The distinct args also keep this insert in its own uniqueness namespace, so it never moves or cancels the regular chain's next scheduled tick — see insert_forced_poll_job/0.

Returns

  • {:ok, job} - Successfully created immediate job
  • {:error, reason} - Failed to create job

Examples

iex> PhoenixKit.Modules.Emails.SQSPollingManager.poll_now()
{:ok, %Oban.Job{}}

set_polling_interval(interval_ms)

Sets the polling interval in milliseconds.

The new interval will be used for subsequent job scheduling.

Parameters

  • interval_ms - Interval in milliseconds (minimum 1000ms)

Returns

  • {:ok, setting} - Successfully updated
  • {:error, reason} - Failed to update

Examples

iex> PhoenixKit.Modules.Emails.SQSPollingManager.set_polling_interval(3000)
{:ok, %Setting{}}

status()

Returns the current status of SQS polling.

Returns

A map with:

  • enabled - Whether polling is enabled
  • interval_ms - Current polling interval
  • pending_jobs - Number of scheduled jobs
  • last_run - Timestamp of last completed job (if any)
  • queue_url - Configured SQS queue URL

Examples

iex> PhoenixKit.Modules.Emails.SQSPollingManager.status()
%{
  enabled: true,
  interval_ms: 5000,
  pending_jobs: 1,
  last_run: ~U[2025-09-20 15:30:45Z],
  queue_url: "https://sqs.eu-north-1.amazonaws.com/..."
}