Architecture Overview

Akismet sits between Drupal's form system and the Akismet cloud API. When a visitor submits a comment, contact form, webform, or registration, Akismet intercepts the submission, sends it to the API for classification, and acts on the verdict — all invisible to the user.

 +-----------+    +------------------+    +--------------+    +-------------+
 |  Visitor  |--->|  Drupal Form API |--->|   Akismet    |--->|   Akismet   |
 |  submits  |    |  (validate phase)|    |   Module     |    |   Cloud API |
 |  form     |    |                  |    |              |    |             |
 +-----------+    +------------------+    +--------------+    +-------------+
                           |                     |                    |
                           |                     |<-------------------+
                           |                     |  verdict: ham / spam / discard / pending
                           v                     v
                  +------------------+    +--------------+
                  |  Entity saved    |    |  Check data  |
                  |  (published or   |    |  persisted   |
                  |   unpublished)   |    |  to DB table |
                  +------------------+    +--------------+

Major Components

1

Form Interception

Hooks into Drupal's form validate phase

AkismetFormHooks attaches validation handlers to comment, contact, and registration forms. AkismetWebformHandler does the same for webforms. All logic lives in AkismetFormHandler. Runs at module weight 1 — if Honeypot/CAPTCHA already rejected the form, Akismet skips the API call.

2

Payload Builder

Translates Drupal data → Akismet API format

AkismetPayloadBuilder constructs an SDK Content DTO from form values: author, email, body, IP, user agent, language, 14 bot-detection signals, webhook callback URL, and a server-generated nonce.

3

Bot Detection JS

Client-side behavioral signals

akismet-frontend.js (ported from the WP plugin) passively collects keystroke timing, mouse movement, touch events, scroll counts, and a honeypot field. Stored in sessionStorage, injected as hidden fields on submit. No cookies, no external requests.

4

SDK Wrapper Service

Bridge to the automattic/akismet-sdk package

AkismetService wraps the SDK with Drupal concerns: API key resolution (settings.php > Key module > config), event dispatch (PreCheck/PostCheck/Feedback), rate limiting, circuit breaker gating, and subscription queries.

5

Verdict Store + Entity Hooks

Transfers verdict from validation → entity save

AkismetVerdictStore holds the verdict in memory. AkismetEntityHooks reads it during hook_comment_presave() (unpublishes spam) and hook_comment_insert() (writes check data to the DB).

6

Check Data Table

Custom DB table tracking every spam check

akismet_check_data stores: entity ID/type, result, GUID, original submission (JSON), moderator overrides, retry state, and history. Composite PK (entity_id, entity_type). Powers the spam queue, feedback, recheck, GDPR, and stats.

7

Moderation UI

Admin tools for reviewing Akismet's decisions

Spam tab at /admin/content/comment/spam with Not Spam and Empty Spam actions. Akismet status column + per-row Mark as Spam/Not Spam links on comment admin. Check history on comment view pages. Bulk actions in admin dropdown.

8

Feedback Loop

Moderator corrections improve future accuracy

6 action plugins (comment/contact/webform × spam/ham) reconstruct the original payload, set reporter + commentCheckResponse, and call submitSpam() or submitHam(). Failures queue for retry with exponential backoff.

9

Deferred Verdicts + Webhook

Async resolution when the API needs more time

When check() returns shouldRecheck(), the comment is held as pending. Two paths race: the API POSTs the verdict to /akismet/v1/webhook (fast), or AkismetDeferredRecheckWorker re-checks after the deadline (fallback). Ham publishes; spam stays held.

10

Resilience Layer

Protects the site when the API is down

AkismetCircuitBreaker: 3-state machine (closed → open → half-open) backed by State API. Stops making calls when failures spike, probes after a recovery timer. The module always fails open — API problems never block legitimate submissions.

11

Maintenance Service

Background housekeeping via cron and Drush

AkismetMaintenanceService runs 5 jobs: recheck error records, expire old payloads (GDPR), purge spam comment entities, clean up orphaned check data, and re-queue stale pending records. Both hook_cron() and Drush delegate to this single service.

The Four Verdicts

VerdictWhat HappensComment State
Ham Submission proceeds normally Published
Spam Saved but hidden from public Unpublished, appears in spam queue
Discard Blatant spam — blocked in strict mode for anonymous users Unpublished (moderate) or blocked entirely (strict)
Pending Deferred — held until webhook or queue worker resolves Unpublished until resolved

What Talks to What

AkismetFormHooks --> AkismetFormHandler --> AkismetPayloadBuilder --> AkismetService --> SDK --> API
                                                                             |
                                          AkismetVerdictStore <-------------+
                                                  |
                    AkismetEntityHooks <----------+
                           |
                    AkismetCheckData (DB write)

AkismetWebhookController --> AkismetCheckData (lookup by GUID, update result)
                                    |
                             Entity publish/unpublish side effects

AkismetMaintenanceService --> AkismetCheckData + AkismetService (recheck, purge, cleanup)
       ^
  hook_cron() / Drush

The module never touches entities during REST/programmatic saves — only form submissions trigger checks. Everything is DI-wired through services.yml with interface-based contracts for testability and decoration.