Skip to content

IMAP handler: parse quiz reply and record submission #729

Description

@payamnj

Part of #727. Depends on #728 for the reply format this parses (can be developed in parallel using a hand-crafted test fixture).

Summary

Add IMAP handling for a quiz <token> reply: parse which answers were marked, submit through the existing quiz scoring/grading path, and email the learner their result.

Where this plugs in

  • ImapInterface.handle_email_message (django_email_learning/services/defaults/imap_interface.py) currently recognizes three subject-line commands: enroll <slug>, verify <code>, drop <slug> (_ACCEPTED_COMMANDS-style dispatch, each backed by a small _enroll/_verify/_drop method). Add quiz as a fourth accepted command, dispatching to a new _quiz(argument, imap_connection, email_message) method.
  • This is the first time IMAP handling needs to parse an email body — today only the subject line and From header are read. email_message (an email.message.EmailMessage, policy=default) needs its text body extracted and scanned for [x]/[ ] marks per question.
  • Grading/submission: QuizSubmissionView.process_quiz_submission(token, answers) (django_email_learning/personalised/api/views.py) already implements JWT decoding, enrollment/attempt validation, scoring (calculate_score_and_passed), and QuizSubmission creation, shared today between the AMP view and the plain click-through view. This logic currently lives as a classmethod on a view class — consider extracting the reusable core (decode token → validate → score → persist) into a plain function/service so the IMAP handler can call it without depending on view-layer code.

Behavior

  1. Decode <token> from the subject the same way SendQuizCommand/DeliverySchedule.generate_link() encode it (delivery id/hash, optional question_ids).
  2. Extract the email body and parse marked answers per question. Needs to tolerate:
    • Whitespace variants: [x], [ x ], [X].
    • Quoted-reply artifacts: leading > on quoted lines, mail clients that reflow or HTML-ify plain text on reply.
  3. Validate the sender's From address matches the learner on the delivery's enrollment — same identity check already used for enroll/verify/drop.
  4. On successful, unambiguous parse: submit via the shared scoring path (same one AMP/web use), then send the learner a new result email (score, pass/fail, any retry/next-steps messaging the existing web flow already surfaces) — needs a new email template and likely a new command model (e.g. SendQuizReplyResultCommand), following the existing send_*_command.py pattern.
  5. On failure to parse (ambiguous/missing marks, can't decode token, learner mismatch, etc.): log via the metric recorder (extend or reuse imap_command_handling_failed) and take no further action — no auto-reply, consistent with how other IMAP command failures are handled today (CheckIMAPJob, django_email_learning/jobs/check_imap_job.py).

Out of scope

Tests

  • Extend tests/services/defaults/test_imap_interface.py with cases for: well-formed single-answer reply, well-formed multi-answer reply, ambiguous/ malformed reply (should silently no-op + log), sender email mismatch, expired/invalid token.
  • New test coverage (new file, alongside tests/services/command_models/test_send_quiz_command.py) for the result-notification command/template.
  • Confirm scoring output for a reply-submitted quiz matches what the equivalent AMP/web submission would produce, given the same answers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions