@rassim-medkour/email-risk-triage
v0.1.0
Published
Reusable server-side email risk triage engine with deterministic checks and optional LLM analysis.
Maintainers
Readme
Email Risk Triage
Reusable server-side email risk triage for signup and account-quality workflows.
The package is intentionally side-effect free. It does not create users, disable accounts, delete accounts, send admin emails, or write to a database. It accepts an email address, gathers evidence, and returns a structured verdict that each host application can map to its own account lifecycle.
What It Checks
- Email syntax and normalization.
- Static disposable-domain blocklists.
- DNS MX records using Node's DNS resolver.
- Domain age through RDAP.
- Suspicious local-part signals, such as low-vowel or digit-heavy identifiers.
- Optional LLM classification using a provider adapter.
Verdicts
| Verdict | Meaning |
| ------- | ------- |
| pass | Evidence suggests a normal account. |
| review | Evidence is incomplete, suspicious, or low confidence. |
| reject | Deterministic evidence strongly indicates the email should not pass. |
Callers decide what those verdicts mean. For example, one platform may keep a
new account disabled until pass, while another may only show review in an
admin queue.
Basic Usage
import {
EmailRiskTriageEngine,
RdapDomainAgeProvider,
StaticBlocklistProvider,
createOpenAiCompatibleLlmProvider
} from "@rassim-medkour/email-risk-triage";
const engine = new EmailRiskTriageEngine({
blocklistProvider: new StaticBlocklistProvider(["tempmail.example"]),
domainAgeProvider: new RdapDomainAgeProvider(),
llmProvider: createOpenAiCompatibleLlmProvider({
apiKey: process.env.EMAIL_TRIAGE_LLM_API_KEY!,
model: process.env.EMAIL_TRIAGE_LLM_MODEL || "gpt-4o-mini",
baseUrl: process.env.EMAIL_TRIAGE_LLM_BASE_URL
})
});
const result = await engine.triage("[email protected]");Production responses (full vs redacted evidence)
By default, triage() returns full EmailRiskTriageResult.evidence:
MX targets, domain age, raw SPF/DMARC strings, web-search titles/snippets, optional
fetched page text, and the same structure the LLM consumes. That is ideal for
debugging, admin review tools, or internal audit logs you control.
For user-facing APIs or analytics pipelines where you do not want to ship
third-party page content or DNS record bodies, pass evidenceDetail: "redacted".
The engine still gathers full evidence internally (and passes it to the LLM),
but the returned payload strips raw TXT, parent-zone TXT rollups, and web
bodies while keeping verdict, reasons, confidence, explanation, MX list,
domain age, blocklist flag, and local-part signals.
const publicResult = await engine.triage(email, { evidenceDetail: "redacted" });
// publicResult.evidenceDetail === "redacted"To redact a result you already stored (for example before sending to a client),
use redactEmailRiskEvidence on result.evidence.
Kimi, OpenAI, Gemini, and Ollama Cloud
createOpenAiCompatibleLlmProvider targets OpenAI-compatible chat completion
APIs with JSON schema response formatting. It can be used for OpenAI directly
and for Kimi-style gateways that expose an OpenAI-compatible endpoint.
If you want Ollama Cloud’s OpenAI-compatible host (https://ollama.com/v1)
instead of the native /api adapter, pass the exported constant as baseUrl:
import {
OLLAMA_CLOUD_OPENAI_BASE_URL,
createOpenAiCompatibleLlmProvider
} from "@rassim-medkour/email-risk-triage";
createOpenAiCompatibleLlmProvider({
apiKey: process.env.OLLAMA_API_KEY!,
model: "kimi-k2.5",
baseUrl: OLLAMA_CLOUD_OPENAI_BASE_URL
});For Ollama Cloud, use
createOllamaCloudLlmProvider, which defaults to https://ollama.com/api and
Authorization: Bearer with your API key.
Set OLLAMA_API_KEY in the environment, or pass apiKey explicitly:
import {
EmailRiskTriageEngine,
NodeDnsTxtProvider,
OllamaCloudWebPresenceProvider,
createOllamaCloudLlmProvider
} from "@rassim-medkour/email-risk-triage";
const engine = new EmailRiskTriageEngine({
llmProvider: createOllamaCloudLlmProvider({
model: "kimi-k2.5"
// apiKey: process.env.OLLAMA_API_KEY — optional if the env var is set
}),
dnsTxtProvider: new NodeDnsTxtProvider(),
webPresenceProvider: new OllamaCloudWebPresenceProvider({
fetchTopResults: 1
})
});Local-style model names such as kimi-k2.5:cloud are also accepted and normalized
to the direct cloud model name before calling ollama.com.
Local Ollama’s OpenAI-compatible endpoint (http://localhost:11434/v1) still
works via createOpenAiCompatibleLlmProvider with baseUrl and a dummy
apiKey if required; only cloud access on ollama.com needs a real key.
The native Ollama Cloud provider uses Ollama's structured-output format field
with the same JSON schema the generic OpenAI-compatible provider uses.
OllamaCloudWebPresenceProvider calls Ollama's
web_search and web_fetch
endpoints, then passes search snippets and fetched page content into the LLM
evidence. This lets the model reason from actual search evidence instead of
pretending it checked the internet.
Gemini should be added as a separate adapter if first-class Gemini support is needed, because its structured-output and function-calling APIs have different request shapes.
Server-Side Evidence
DNS MX, optional DNS TXT (SPF/DMARC), RDAP, and optional web-search checks run on the server. The LLM receives facts gathered by code and should not be asked to invent live DNS, WHOIS, blocklist, or web-index state.
This keeps the package:
- Cheaper, because deterministic rejections avoid paid model calls.
- Faster, because local checks happen before LLM classification.
- More reliable, because network evidence is collected by code, not guessed by a model.
- Reusable, because each host platform can provide its own blocklist, cache, logger, and enforcement policy.
Development
Requires Node 18 or newer because the package uses native fetch,
AbortController, and node:dns.
npm install
npm test
npm run test:ollama # optional live Ollama Cloud smoke test
npm run typecheck
npm run buildFor the live Ollama Cloud smoke test, copy .env.example to .env, set
OLLAMA_API_KEY, then run:
npm run test:ollamaYou can pass a different email as an argument:
npm run test:ollama -- [email protected]By default the script prints evidenceDetail: "redacted" (no raw SPF/DMARC
strings or web page bodies in JSON). The LLM still sees full evidence internally.
For a verbose dump (like older behavior), use either:
npm run test:ollama -- --full [email protected]
# or
EMAIL_TRIAGE_EVIDENCE_DETAIL=full npm run test:ollama -- [email protected]The smoke test uses real DNS MX/TXT, RDAP, and Ollama web_search/web_fetch
evidence. It can consume extra web-search and web-fetch quota.
The default new-domain threshold is 30 days. The deterministic policy flags domains with an age strictly below the threshold; a domain exactly 30 days old is eligible for LLM review or pass handling depending on the rest of the evidence.
Current Boundaries
Included:
- Core triage engine.
- Static blocklist provider.
- Node DNS MX provider.
- Node DNS TXT provider for SPF/DMARC evidence.
- RDAP domain-age provider.
- OpenAI-compatible structured-output adapter.
- Ollama Cloud helper (
createOllamaCloudLlmProvider,OLLAMA_API_KEY). - Ollama Cloud web-search evidence provider (
OllamaCloudWebPresenceProvider).
Deferred to host applications:
- Signup integration.
- Account enable/disable behavior.
- Admin emails.
- Dashboards or persistence.
- Queueing and scheduled batch jobs.
