Skip to content

External integrations

Alerts, the Inbox and a paper's PDF are core ALMa features. They do not become optional merely because no external service is connected. Integration plugins are the adapters that let those core features reach outside — to Slack, to an SMTP server, to a PDF host.

Each plugin declares which of three capabilities it implements:

Capability Core owner Integration responsibility
send Alerts selects papers, schedules, deduplicates, and records outcomes Render and deliver the finished digest
receive Inbox resolves papers, lands corpus rows, deduplicates, and owns triage Fetch external messages and acknowledge outcomes
pdf_source Paper PDFs runs the sources in order, verifies the file against the paper, and stores it Contribute candidate sources and fetch a file from each

Four ship: Slack (send + receive), Email (send), and two PDF sources, Open-access PDFs and Shadow libraries. One Slack activation controls both of its directions because they share one integration and one credential; status still reports each direction separately.

Every plugin's own setup lives on its page. Plugins is the index, and states the rules that hold for all of them.

One explicit manifest registry

alma.plugins.registry.PLUGINS is the only integration catalogue. Every entry is a PluginManifest with:

  • stable identity and version;
  • its declared capabilities;
  • one explicit activation flag;
  • one strict Pydantic configuration model;
  • generated JSON Schema, storage mapping, masked secret reads, and status;
  • optional Alert sender, Inbox adapter, PDF sources, and connectivity-test action.

Registration is explicit. An unregistered id cannot enter an Alert row, deliver a digest, put a message into Inbox, or be asked for a PDF. Not in this registry: anything that is a native intelligence feature.

The capability protocols remain separate. Outbound integrations implement the manifest's AlertSender callback; inbound integrations implement alma.application.inbox_schema.InboundChannel; PDF integrations return PdfSource values from pdf_source_factory. A send-only integration does not grow fake receive methods.

Activation is not deletion

Every plugin ships switched off, and PluginManifest enforces that itself rather than trusting each package to check: while a plugin is off, inbound_channel() yields nothing and send_alert() / test_connection() raise PluginDisabledError, which the API returns as 409. Only self-description, configuration and status() stay available — and status_factory must be local and side-effect-free, so listing plugins never loads a transport.

Turning one off again in Settings → Plugins retains its configuration and secrets. ALMa then:

  • excludes it from new delivery choices and automated Alert sends;
  • excludes its inbound adapter from Inbox capture sweeps;
  • excludes its sources from PDF fetches;
  • hides its direction-status pills on Home.

Switch it back on and the connectivity test is available again. Activation never purges Inbox papers or Alert history.

One transport and one credential

Everything Slack goes through alma.slack.client.SlackNotifier: token, slack_sdk client, channel resolution, posting, history reads, and reactions. The Slack manifest and Inbox adapter call that transport; neither owns another HTTP client.

Secrets live only in alma.core.secrets. Settings shows masked values and the server-generated schema marks secret fields with x-alma-secret. A legacy config/slack.* file is imported once by storage migration and never read by runtime code.

API

Method Path Purpose
GET /api/v1/plugins All manifests, capabilities, schema, activation, and status
GET /api/v1/plugins/{id} One manifest
PUT /api/v1/plugins/{id}/enabled Activate/deactivate without deleting config
GET /api/v1/plugins/{id}/config Read validated config with masked secrets
PUT /api/v1/plugins/{id}/config Strictly validate and replace config
POST /api/v1/plugins/{id}/test Run the manifest test through Activity
GET /api/v1/inbox/status Core Inbox capture status
POST /api/v1/inbox/sweep Core Inbox action: poll active receive adapters

The same Pydantic model validates writes and produces config_schema; the frontend does not maintain a duplicate field list.

For each plugin's setup, see the plugin pages. For the package contract and extension checklist, see Building an integration.