From 1013a4a803352fbed3961f5b0bd00fa72ccf8ca5 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 9 Oct 2026 21:00:01 +0000 Subject: [PATCH] Add email verification inputs Add the verification_time and was_verification_successful inputs to the email object. They describe the most recent verification of the email address. Validation accepts a boolean for the result and an RFC 3339 date-time for the time. Co-Authored-By: Claude Opus 5.5 --- HISTORY.rst | 3 +++ README.rst | 4 +++- src/minfraud/validation.py | 2 ++ tests/data/full-transaction-request.json | 4 +++- tests/test_validation.py | 13 +++++++++++++ tests/test_webservice.py | 10 +++++++++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 139f21b..315c242 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,9 @@ History ``phone_was_verification_successful``, and ``phone_verification_time`` inputs to the ``/billing`` and ``/shipping`` request objects. They describe the most recent verification of the phone number. +* Added the ``verification_time`` and ``was_verification_successful`` inputs + to the ``/email`` request object. They describe the most recent + verification of the email address. * The RFC 3339 check on ``/event/time`` and ``phone_verification_time`` now rejects more values that the web service rejects, such as February 30, hour 24, an offset of 24 hours or more, and a lowercase ``t`` or ``z``. diff --git a/README.rst b/README.rst index 961ec22..bcc147f 100644 --- a/README.rst +++ b/README.rst @@ -169,7 +169,9 @@ Score, Insights and Factors Example >>> }, >>> 'email': { >>> 'address': '977577b140bfb7c516e4746204fbdb01', - >>> 'domain': 'maxmind.com' + >>> 'domain': 'maxmind.com', + >>> 'verification_time': '2026-10-01T14:25:00Z', + >>> 'was_verification_successful': True, >>> }, >>> 'billing': { >>> 'first_name': 'Jane', diff --git a/src/minfraud/validation.py b/src/minfraud/validation.py index 59b5c39..821915d 100644 --- a/src/minfraud/validation.py +++ b/src/minfraud/validation.py @@ -393,6 +393,8 @@ def _uri(s: str) -> str: "email": { "address": _email_or_md5, "domain": _hostname, + "verification_time": _rfc3339_datetime, + "was_verification_successful": bool, }, "event": { "party": _event_party, diff --git a/tests/data/full-transaction-request.json b/tests/data/full-transaction-request.json index 985d333..e9c5874 100644 --- a/tests/data/full-transaction-request.json +++ b/tests/data/full-transaction-request.json @@ -12,7 +12,9 @@ }, "email": { "address": "977577b140bfb7c516e4746204fbdb01", - "domain": "maxmind.com" + "domain": "maxmind.com", + "verification_time": "2026-10-01T14:25:00Z", + "was_verification_successful": true }, "billing": { "first_name": "First", diff --git a/tests/test_validation.py b/tests/test_validation.py index 3207085..d526cb7 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -371,6 +371,19 @@ def test_domain(self) -> None: for bad in ("bad ", " bad.com"): self.check_invalid_transaction({"email": {"domain": bad}}) + def test_verification_time(self) -> None: + for good in GOOD_TIMES: + self.check_transaction({"email": {"verification_time": good}}) + for bad in BAD_TIMES: + self.check_invalid_transaction({"email": {"verification_time": bad}}) + + def test_verification_time_not_string(self) -> None: + for bad in NON_STRING_TIMES: + self.check_invalid_transaction({"email": {"verification_time": bad}}) + + def test_was_verification_successful(self) -> None: + self.check_bool("email", "was_verification_successful") + class TestEvent(ValidationBase, unittest.TestCase): def test_party(self) -> None: diff --git a/tests/test_webservice.py b/tests/test_webservice.py index d949642..f55f7b5 100644 --- a/tests/test_webservice.py +++ b/tests/test_webservice.py @@ -281,6 +281,8 @@ def test_200_with_email_hashing(self) -> None: "email": { "address": "977577b140bfb7c516e4746204fbdb01", "domain": "maxmind.com", + "verification_time": "2026-10-01T14:25:00Z", + "was_verification_successful": False, }, }, ).respond_with_data( @@ -292,7 +294,13 @@ def test_200_with_email_hashing(self) -> None: status=200, ) - request = {"email": {"address": "Test+ignore@maxmind.com"}} + request = { + "email": { + "address": "Test+ignore@maxmind.com", + "verification_time": "2026-10-01T14:25:00Z", + "was_verification_successful": False, + }, + } self.run_client(getattr(self.client, self.type)(request, hash_email=True)) # This was fixed in https://github.com/maxmind/minfraud-api-python/pull/78