Startup Checks

Call DripDrop.startup_check/0 during host boot. The check validates channel configuration, optional provider dependencies, encryption key decoding, scheduler callbacks, unsubscribe URL builder requirements, and PgFlow job registration when DripDrop.Schedulers.Pgflow is configured.

When PgFlow is the scheduler, every module listed in config :dripdrop, :pgflow, jobs: ... must have its PgFlow job migration generated and migrated. The default setup uses DripDrop.Jobs.DispatchStep and DripDrop.Jobs.CronTick.

Schema Checks

Run:

mix dripdrop.check_schema

Use this in CI and deploy smoke checks.

Integration Tests

Integration tests exercise the real PgFlow scheduler, worker polling, webhook ingest, and database state transitions. Provider HTTP calls stay local through Req.Test; tests must not call real provider networks.

Run them locally after the test database is up:

docker compose up -d
mix test --only integration

Add new integration tests with DripDrop.IntegrationCase and @moduletag :integration. Use DripDrop.TestSupport.PgflowHarness when a test needs a PgFlow worker, and prefer eventually/2 over fixed sleeps for state that changes in worker processes.

When debugging PgFlow timing flakes:

  • Lower min_poll_interval / max_poll_interval in PgflowHarness.child_spec/1.
  • Increase the relevant eventually/2 timeout before adding sleeps.
  • Inspect pgflow.step_tasks, pgflow.runs, and pgmq.q_dispatch_step to see whether work is queued, started, failed, or invisible due to retry delay.
  • Keep provider stubs in Req.Test shared mode when the request is made from a PgFlow worker process.

Telemetry

Attach handlers to DripDrop.Telemetry.events/0 for dispatch, policy, provider ingest, template, hook, short-link, and channel events.

:telemetry.attach_many(
  "dripdrop-ops",
  DripDrop.Telemetry.events(),
  &MyApp.Telemetry.handle_event/4,
  nil
)

Provider Events

DripDrop.Web.WebhookPlug verifies provider signatures before normalization. Duplicate provider events skip duplicate persistence and emit telemetry. Duplicate replies can still run configured reply handling.

Deleting a Tenant

When a host deletes an account, remove its DripDrop data with:

{:ok, deleted_counts} = DripDrop.delete_tenant(tenant_key)

It deletes every row whose tenant_key matches, across all DripDrop tables, in one transaction, and returns the deleted count per table. Global rows (tenant_key: nil) and other tenants' rows are never selected. A nil or blank key raises ArgumentError.

If a row outside the tenant references one of the tenant's rows, the delete would cascade into or null out that row, so nothing is deleted and the call returns {:error, {:cross_tenant_references, references}}, listing each %{table:, column:, count:}.

Uninstall

Generate the uninstall SQL:

mix dripdrop.uninstall

The task prints the destructive SQL for an operator to review; it does not drop the schema automatically.