DripDrop is an Elixir library that stores its domain data in the dripdrop Postgres schema and uses a host application repo.

Requirements

DripDrop requires Elixir/OTP compatible with the project .tool-versions, Postgres 18+ (for native uuidv7()), a host Ecto repo, and DRIPDROP_ENCRYPTION_KEY set to a base64-encoded 32-byte key.

Dependencies

Add DripDrop to the host app:

{:dripdrop, "~> 0.1"}

For local path development:

{:dripdrop, path: "../dripdrop"}

Optional provider packages such as Swoosh, Finch, MJML, Phoenix PubSub, Oban, AWS SNS, ExGram, and whatsapp_sdk are loaded only when the matching provider is used. Liquex is required for Liquid template rendering.

Configuration

config :dripdrop,
  repo: MyApp.Repo,
  scheduler: DripDrop.Schedulers.Pgflow,
  channels: [],
  quiet_hours_default: {8, 21},
  sms_max_chars: 1600

config :dripdrop, :pgflow,
  jobs: [DripDrop.Jobs.DispatchStep, DripDrop.Jobs.CronTick]

Set DRIPDROP_ENCRYPTION_KEY before boot. It must be a base64-encoded key accepted by DripDrop.Vault.

For host apps that already run Oban, set scheduler: DripDrop.Schedulers.Oban and configure the host Oban supervision tree with a :dripdrop queue.

Supervision

The PgFlow scheduler only enqueues work; something still has to poll pgmq and execute it. Add a {PgFlow, ...} child to the host application's supervision tree, after the host Ecto repo and before the Endpoint:

children = [
  MyApp.Repo,
  {PgFlow,
   repo: MyApp.Repo,
   jobs: [DripDrop.Jobs.DispatchStep, DripDrop.Jobs.CronTick],
   signal_strategy: :notify,
   notify_throttle_ms: 50,
   notify_fallback_interval: 250},
  MyAppWeb.Endpoint
]

List every job module that must run under this worker, not just DripDrop's own DispatchStep and CronTick — any host job modules (use PgFlow.Job) belong in the same jobs: list, since PgFlow only starts a poller for job modules it is told about. The jobs: list under config :dripdrop, :pgflow is a separate, DripDrop-only setting that DripDrop.startup_check/0 reads to confirm DispatchStep was wired up; it does not register anything with PgFlow, so host jobs never need to be listed there.

Without this child, DripDrop.startup_check/0 reports pgflow_job_not_registered and DripDrop dispatches never execute — enrollment enqueues rows that sit in pgmq forever.

Omit this child (or gate it behind a config flag) in test environments that configure scheduler: DripDrop.Schedulers.Test: a live poller would race to claim and execute pgmq rows that other tests enqueue.

Migrations

PgFlow is the recommended scheduler. Generate PgFlow migrations first:

mix pgflow.gen.postgres_extensions_migration
mix pgflow.gen.pgmq_migration
mix pgflow.setup
mix pgflow.gen.job_migration DripDrop.Jobs.DispatchStep
mix pgflow.gen.job_migration DripDrop.Jobs.CronTick

DispatchStep is the generic worker DripDrop uses for scheduled sends. CronTick seeds due cron-style DripDrop steps; keep it configured and compiled when cron timing is enabled. For hosts without pg_cron, generate PgFlow extensions with --no-cron and keep CronTick in the configured PgFlow job list.

These are one-time scheduler/job setup migrations. DripDrop sequence authoring is dynamic: creating new sequences, steps, transitions, conditions, hooks, or enrollments does not require new PgFlow migrations.

Generate the DripDrop wrapper migration:

mix dripdrop.setup --repo MyApp.Repo

Run host migrations after scheduler migrations are present:

mix ecto.migrate

Use mix dripdrop.check_schema in CI or deploy checks to verify the installed schema version matches the library.

Cold Outbound

Cold outbound is optional. No extra migration is required beyond the current V01 schema. Hosts that enable outbound mode should:

  1. Create mailbox or ESP adapters with health/ramp fields.
  2. Create an adapter pool and add pool members.
  3. Set sequence_versions.mode = "outbound" and config["pool_id"] on the outbound version.
  4. Pump inbound mailbox replies from host-owned IMAP, Microsoft Graph, or Gmail infrastructure into DripDrop.ingest_inbound_message/2.

See cold_outbound.md for the full operator guide.

Webhooks

In a Phoenix router:

import DripDrop.Web.Router

scope "/" do
  dripdrop_webhooks("/webhooks/dripdrop")
end

Inbound routes are registered for Mailgun, SendGrid, Postmark, MailerSend, SES, and Twilio. Verification happens in DripDrop.Web.WebhookPlug; supported providers use their native signature, basic auth, or SNS certificate checks. Gmail, MS365, SMTP, PubSub, Slack, and Telegram treat a successful send as the terminal positive signal.