From fa9edf63ac9f173287569ab56508d5c4f1c4e09c Mon Sep 17 00:00:00 2001 From: pengfeiye Date: Tue, 17 Feb 2026 15:26:18 -0500 Subject: [PATCH 1/2] Add 'smtp_required' option to Python SDK hosted auth --- CHANGELOG.md | 1 + nylas/models/auth.py | 3 +++ nylas/resources/auth.py | 3 +++ tests/resources/test_auth.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ee02b..c8ef131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Unreleased ---------- * UAS multi-credential update * Added `specific_time_availability` field to `AvailabilityParticipant` for overriding open hours on specific dates +* Added `smtp_required` option to hosted authentication config to require users to enter SMTP settings during IMAP authentication v6.14.2 ---------- diff --git a/nylas/models/auth.py b/nylas/models/auth.py index 2246478..6c83628 100644 --- a/nylas/models/auth.py +++ b/nylas/models/auth.py @@ -36,6 +36,8 @@ class URLForAuthenticationConfig(TypedDict): state: Optional state to be returned after authentication login_hint: Prefill the login name (usually email) during authorization flow. If a Grant for the provided email already exists, a Grant's re-auth will automatically be initiated. + smtp_required: If True, adds options=smtp_required so users must enter SMTP settings during + authentication. Relevant for IMAP; avoids grant errors when sending email later. """ client_id: str @@ -48,6 +50,7 @@ class URLForAuthenticationConfig(TypedDict): state: NotRequired[str] login_hint: NotRequired[str] credential_id: NotRequired[str] + smtp_required: NotRequired[bool] class URLForAdminConsentConfig(URLForAuthenticationConfig): diff --git a/nylas/resources/auth.py b/nylas/resources/auth.py index 18dfc65..9a44e90 100644 --- a/nylas/resources/auth.py +++ b/nylas/resources/auth.py @@ -35,6 +35,9 @@ def _build_query(config: dict) -> dict: if "scope" in config: config["scope"] = " ".join(config["scope"]) + if config.get("smtp_required"): + config["options"] = "smtp_required" + return config diff --git a/tests/resources/test_auth.py b/tests/resources/test_auth.py index f592de8..0d33576 100644 --- a/tests/resources/test_auth.py +++ b/tests/resources/test_auth.py @@ -37,6 +37,29 @@ def test_build_query(self): "scope": "email calendar", } + def test_build_query_with_smtp_required_true(self): + config = { + "foo": "bar", + "scope": ["email"], + "smtp_required": True, + } + result = _build_query(config) + assert result["options"] == "smtp_required" + + def test_build_query_smtp_required_false_omits_options(self): + config = { + "foo": "bar", + "scope": ["email"], + "smtp_required": False, + } + result = _build_query(config) + assert "options" not in result + + def test_build_query_smtp_required_omitted_omits_options(self): + config = {"foo": "bar", "scope": ["email"]} + result = _build_query(config) + assert "options" not in result + def test_build_query_with_pkce(self): config = { "foo": "bar", @@ -52,6 +75,17 @@ def test_build_query_with_pkce(self): "code_challenge_method": "s256", } + def test_build_query_with_pkce_and_smtp_required(self): + config = { + "foo": "bar", + "scope": ["email"], + "smtp_required": True, + } + result = _build_query_with_pkce(config, "secret-hash-123") + assert result["options"] == "smtp_required" + assert result["code_challenge"] == "secret-hash-123" + assert result["code_challenge_method"] == "s256" + def test_build_query_with_admin_consent(self): config = { "foo": "bar", From 55e7b5fb68d9b8096a9cd9937640b694b807885a Mon Sep 17 00:00:00 2001 From: pengfeiye Date: Wed, 18 Feb 2026 11:22:19 -0500 Subject: [PATCH 2/2] addressing comment --- nylas/resources/auth.py | 2 +- tests/resources/test_auth.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nylas/resources/auth.py b/nylas/resources/auth.py index 9a44e90..6129bb2 100644 --- a/nylas/resources/auth.py +++ b/nylas/resources/auth.py @@ -35,7 +35,7 @@ def _build_query(config: dict) -> dict: if "scope" in config: config["scope"] = " ".join(config["scope"]) - if config.get("smtp_required"): + if config.pop("smtp_required", None): config["options"] = "smtp_required" return config diff --git a/tests/resources/test_auth.py b/tests/resources/test_auth.py index 0d33576..8548c34 100644 --- a/tests/resources/test_auth.py +++ b/tests/resources/test_auth.py @@ -45,6 +45,7 @@ def test_build_query_with_smtp_required_true(self): } result = _build_query(config) assert result["options"] == "smtp_required" + assert "smtp_required" not in result # must not leak into URL params def test_build_query_smtp_required_false_omits_options(self): config = { @@ -83,6 +84,7 @@ def test_build_query_with_pkce_and_smtp_required(self): } result = _build_query_with_pkce(config, "secret-hash-123") assert result["options"] == "smtp_required" + assert "smtp_required" not in result # must not leak into URL params assert result["code_challenge"] == "secret-hash-123" assert result["code_challenge_method"] == "s256"