langchain-verifly 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_verifly-0.1.0/LICENSE +21 -0
- langchain_verifly-0.1.0/PKG-INFO +131 -0
- langchain_verifly-0.1.0/README.md +101 -0
- langchain_verifly-0.1.0/langchain_verifly/__init__.py +18 -0
- langchain_verifly-0.1.0/langchain_verifly/py.typed +0 -0
- langchain_verifly-0.1.0/langchain_verifly/tool.py +156 -0
- langchain_verifly-0.1.0/langchain_verifly.egg-info/PKG-INFO +131 -0
- langchain_verifly-0.1.0/langchain_verifly.egg-info/SOURCES.txt +12 -0
- langchain_verifly-0.1.0/langchain_verifly.egg-info/dependency_links.txt +1 -0
- langchain_verifly-0.1.0/langchain_verifly.egg-info/requires.txt +6 -0
- langchain_verifly-0.1.0/langchain_verifly.egg-info/top_level.txt +1 -0
- langchain_verifly-0.1.0/pyproject.toml +57 -0
- langchain_verifly-0.1.0/setup.cfg +4 -0
- langchain_verifly-0.1.0/tests/test_tool.py +114 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Verifly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-verifly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting Verifly (agent-native email verification) and LangChain
|
|
5
|
+
Author-email: Verifly <support@verifly.email>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://verifly.email
|
|
8
|
+
Project-URL: Repository, https://github.com/james-sib/langchain-verifly
|
|
9
|
+
Project-URL: Source Code, https://github.com/james-sib/langchain-verifly/tree/master/langchain_verifly
|
|
10
|
+
Keywords: langchain,verifly,email-verification,email-validation,deliverability,agents,tools
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: langchain-core<0.4,>=0.3
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7.4; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# langchain-verifly
|
|
32
|
+
|
|
33
|
+
LangChain integration for [Verifly](https://verifly.email) — **agent-native email verification**.
|
|
34
|
+
|
|
35
|
+
`langchain-verifly` gives any LangChain agent a single, well-described tool to
|
|
36
|
+
check whether an email address is real and deliverable *before* it sends a
|
|
37
|
+
message or accepts a signup. It wraps the Verifly verification API and returns a
|
|
38
|
+
clean, structured verdict (deliverable / undeliverable / risky), the reason, a
|
|
39
|
+
confidence score, and an actionable send-or-reject recommendation.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -U langchain-verifly
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Setup
|
|
48
|
+
|
|
49
|
+
Set your Verifly API key in the environment. You can self-onboard for a key
|
|
50
|
+
(with free credits) at [verifly.email](https://verifly.email).
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export VERIFLY_API_KEY="vf_..."
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tool: `VeriflyEmailVerifier`
|
|
57
|
+
|
|
58
|
+
A LangChain `BaseTool` with both sync and async support.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
62
|
+
|
|
63
|
+
verifier = VeriflyEmailVerifier() # reads VERIFLY_API_KEY from the environment
|
|
64
|
+
|
|
65
|
+
verifier.invoke({"email": "lead@example.com"})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
{
|
|
70
|
+
"email": "lead@example.com",
|
|
71
|
+
"result": "deliverable",
|
|
72
|
+
"reason": "Mailbox exists",
|
|
73
|
+
"confidence": 95,
|
|
74
|
+
"is_valid": True,
|
|
75
|
+
"recommendation": "send",
|
|
76
|
+
"did_you_mean": None,
|
|
77
|
+
"details": {
|
|
78
|
+
"syntax_valid": True,
|
|
79
|
+
"domain_exists": True,
|
|
80
|
+
"mx_records": True,
|
|
81
|
+
"smtp_valid": True,
|
|
82
|
+
"is_disposable": False,
|
|
83
|
+
"is_role_account": False,
|
|
84
|
+
"is_catch_all": False,
|
|
85
|
+
"is_free_provider": False,
|
|
86
|
+
"provider": "example.com",
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Async works too:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
await verifier.ainvoke({"email": "lead@example.com"})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
You can also pass the key explicitly instead of using the environment variable:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
verifier = VeriflyEmailVerifier(api_key="vf_...")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Use it in an agent
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from langchain.agents import create_react_agent # or any agent constructor
|
|
107
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
108
|
+
|
|
109
|
+
tools = [VeriflyEmailVerifier()]
|
|
110
|
+
# ... wire `tools` into your agent / model.bind_tools(tools) as usual
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The agent can now verify an address on its own — for example, scrubbing a lead
|
|
114
|
+
list, validating a user-supplied email at signup, or confirming a contact
|
|
115
|
+
before drafting an outreach email.
|
|
116
|
+
|
|
117
|
+
## Verdict fields
|
|
118
|
+
|
|
119
|
+
| Field | Meaning |
|
|
120
|
+
|-------|---------|
|
|
121
|
+
| `result` | `deliverable`, `undeliverable`, or `risky` |
|
|
122
|
+
| `reason` | Human-readable explanation of the verdict |
|
|
123
|
+
| `confidence` | Confidence score, 0–100 |
|
|
124
|
+
| `is_valid` | Whether the address is considered valid/usable |
|
|
125
|
+
| `recommendation` | `send`, `do_not_send`, or `risky` |
|
|
126
|
+
| `did_you_mean` | A suggested correction for likely typos, if any |
|
|
127
|
+
| `details` | Syntax / domain / MX / SMTP / disposable / role / catch-all / free-provider flags |
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# langchain-verifly
|
|
2
|
+
|
|
3
|
+
LangChain integration for [Verifly](https://verifly.email) — **agent-native email verification**.
|
|
4
|
+
|
|
5
|
+
`langchain-verifly` gives any LangChain agent a single, well-described tool to
|
|
6
|
+
check whether an email address is real and deliverable *before* it sends a
|
|
7
|
+
message or accepts a signup. It wraps the Verifly verification API and returns a
|
|
8
|
+
clean, structured verdict (deliverable / undeliverable / risky), the reason, a
|
|
9
|
+
confidence score, and an actionable send-or-reject recommendation.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install -U langchain-verifly
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Set your Verifly API key in the environment. You can self-onboard for a key
|
|
20
|
+
(with free credits) at [verifly.email](https://verifly.email).
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export VERIFLY_API_KEY="vf_..."
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Tool: `VeriflyEmailVerifier`
|
|
27
|
+
|
|
28
|
+
A LangChain `BaseTool` with both sync and async support.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
32
|
+
|
|
33
|
+
verifier = VeriflyEmailVerifier() # reads VERIFLY_API_KEY from the environment
|
|
34
|
+
|
|
35
|
+
verifier.invoke({"email": "lead@example.com"})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
{
|
|
40
|
+
"email": "lead@example.com",
|
|
41
|
+
"result": "deliverable",
|
|
42
|
+
"reason": "Mailbox exists",
|
|
43
|
+
"confidence": 95,
|
|
44
|
+
"is_valid": True,
|
|
45
|
+
"recommendation": "send",
|
|
46
|
+
"did_you_mean": None,
|
|
47
|
+
"details": {
|
|
48
|
+
"syntax_valid": True,
|
|
49
|
+
"domain_exists": True,
|
|
50
|
+
"mx_records": True,
|
|
51
|
+
"smtp_valid": True,
|
|
52
|
+
"is_disposable": False,
|
|
53
|
+
"is_role_account": False,
|
|
54
|
+
"is_catch_all": False,
|
|
55
|
+
"is_free_provider": False,
|
|
56
|
+
"provider": "example.com",
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Async works too:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
await verifier.ainvoke({"email": "lead@example.com"})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You can also pass the key explicitly instead of using the environment variable:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
verifier = VeriflyEmailVerifier(api_key="vf_...")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Use it in an agent
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from langchain.agents import create_react_agent # or any agent constructor
|
|
77
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
78
|
+
|
|
79
|
+
tools = [VeriflyEmailVerifier()]
|
|
80
|
+
# ... wire `tools` into your agent / model.bind_tools(tools) as usual
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The agent can now verify an address on its own — for example, scrubbing a lead
|
|
84
|
+
list, validating a user-supplied email at signup, or confirming a contact
|
|
85
|
+
before drafting an outreach email.
|
|
86
|
+
|
|
87
|
+
## Verdict fields
|
|
88
|
+
|
|
89
|
+
| Field | Meaning |
|
|
90
|
+
|-------|---------|
|
|
91
|
+
| `result` | `deliverable`, `undeliverable`, or `risky` |
|
|
92
|
+
| `reason` | Human-readable explanation of the verdict |
|
|
93
|
+
| `confidence` | Confidence score, 0–100 |
|
|
94
|
+
| `is_valid` | Whether the address is considered valid/usable |
|
|
95
|
+
| `recommendation` | `send`, `do_not_send`, or `risky` |
|
|
96
|
+
| `did_you_mean` | A suggested correction for likely typos, if any |
|
|
97
|
+
| `details` | Syntax / domain / MX / SMTP / disposable / role / catch-all / free-provider flags |
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""LangChain integration for Verifly — agent-native email verification.
|
|
2
|
+
|
|
3
|
+
Exposes :class:`VeriflyEmailVerifier`, a LangChain ``BaseTool`` that verifies
|
|
4
|
+
the deliverability of an email address through the Verifly API
|
|
5
|
+
(https://verifly.email) and returns a structured verdict (result, reason,
|
|
6
|
+
confidence, and deliverability flags).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib import metadata
|
|
10
|
+
|
|
11
|
+
from langchain_verifly.tool import VeriflyEmailVerifier, VeriflyToolInput
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = metadata.version(__package__)
|
|
15
|
+
except metadata.PackageNotFoundError: # pragma: no cover - local source tree
|
|
16
|
+
__version__ = ""
|
|
17
|
+
|
|
18
|
+
__all__ = ["VeriflyEmailVerifier", "VeriflyToolInput", "__version__"]
|
|
File without changes
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Verifly email-verification tool for LangChain."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from langchain_core.callbacks import (
|
|
10
|
+
AsyncCallbackManagerForToolRun,
|
|
11
|
+
CallbackManagerForToolRun,
|
|
12
|
+
)
|
|
13
|
+
from langchain_core.tools import BaseTool
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
DEFAULT_BASE_URL = "https://verifly.email"
|
|
17
|
+
_VERIFY_PATH = "/api/v1/verify"
|
|
18
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VeriflyToolInput(BaseModel):
|
|
22
|
+
"""Input schema for :class:`VeriflyEmailVerifier`."""
|
|
23
|
+
|
|
24
|
+
email: str = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="The email address to verify, e.g. 'lead@example.com'.",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VeriflyEmailVerifier(BaseTool):
|
|
31
|
+
"""Verify the deliverability of an email address with Verifly.
|
|
32
|
+
|
|
33
|
+
Agent-native email verification: given a single email address, this tool
|
|
34
|
+
calls the Verifly API (``GET /api/v1/verify``) and returns a structured
|
|
35
|
+
verdict describing whether the address is deliverable, undeliverable, or
|
|
36
|
+
risky, along with the reason, a confidence score, and the actionable
|
|
37
|
+
send/reject recommendation. Use it to validate a lead or signup before
|
|
38
|
+
sending mail.
|
|
39
|
+
|
|
40
|
+
Setup:
|
|
41
|
+
Install the package and set your Verifly API key in the environment.
|
|
42
|
+
|
|
43
|
+
.. code-block:: bash
|
|
44
|
+
|
|
45
|
+
pip install -U langchain-verifly
|
|
46
|
+
export VERIFLY_API_KEY="vf_..."
|
|
47
|
+
|
|
48
|
+
A key (with free credits) can be obtained at https://verifly.email.
|
|
49
|
+
|
|
50
|
+
Instantiate:
|
|
51
|
+
.. code-block:: python
|
|
52
|
+
|
|
53
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
54
|
+
|
|
55
|
+
verifier = VeriflyEmailVerifier()
|
|
56
|
+
# or pass the key explicitly:
|
|
57
|
+
verifier = VeriflyEmailVerifier(api_key="vf_...")
|
|
58
|
+
|
|
59
|
+
Invoke directly:
|
|
60
|
+
.. code-block:: python
|
|
61
|
+
|
|
62
|
+
verifier.invoke({"email": "lead@example.com"})
|
|
63
|
+
|
|
64
|
+
The tool returns a dict such as::
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
"email": "lead@example.com",
|
|
68
|
+
"result": "deliverable",
|
|
69
|
+
"reason": "Mailbox exists",
|
|
70
|
+
"confidence": 95,
|
|
71
|
+
"is_valid": True,
|
|
72
|
+
"recommendation": "send",
|
|
73
|
+
"details": {...},
|
|
74
|
+
}
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
name: str = "verifly_email_verifier"
|
|
78
|
+
description: str = (
|
|
79
|
+
"Verify whether a single email address is deliverable using Verifly. "
|
|
80
|
+
"Input is one email address. Returns the verdict (deliverable, "
|
|
81
|
+
"undeliverable, or risky), the reason, a confidence score (0-100), "
|
|
82
|
+
"and a send/reject recommendation. Use before emailing a lead or "
|
|
83
|
+
"accepting a signup to avoid bounces and bad addresses."
|
|
84
|
+
)
|
|
85
|
+
args_schema: type[BaseModel] = VeriflyToolInput
|
|
86
|
+
|
|
87
|
+
api_key: Optional[str] = Field(
|
|
88
|
+
default=None,
|
|
89
|
+
description=(
|
|
90
|
+
"Verifly API key. Falls back to the VERIFLY_API_KEY environment "
|
|
91
|
+
"variable when not set."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
base_url: str = Field(
|
|
95
|
+
default=DEFAULT_BASE_URL,
|
|
96
|
+
description="Base URL of the Verifly API.",
|
|
97
|
+
)
|
|
98
|
+
timeout: float = Field(
|
|
99
|
+
default=_DEFAULT_TIMEOUT,
|
|
100
|
+
description="HTTP request timeout in seconds.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _resolve_key(self) -> str:
|
|
104
|
+
key = self.api_key or os.environ.get("VERIFLY_API_KEY")
|
|
105
|
+
if not key:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"A Verifly API key is required. Pass api_key=... or set the "
|
|
108
|
+
"VERIFLY_API_KEY environment variable. Get a free key at "
|
|
109
|
+
"https://verifly.email."
|
|
110
|
+
)
|
|
111
|
+
return key
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _shape(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
115
|
+
"""Reduce the raw API response to the relevant verdict fields."""
|
|
116
|
+
return {
|
|
117
|
+
"email": payload.get("email"),
|
|
118
|
+
"result": payload.get("result"),
|
|
119
|
+
"reason": payload.get("reason"),
|
|
120
|
+
"confidence": payload.get("confidence"),
|
|
121
|
+
"is_valid": payload.get("is_valid"),
|
|
122
|
+
"recommendation": payload.get("recommendation"),
|
|
123
|
+
"did_you_mean": payload.get("did_you_mean"),
|
|
124
|
+
"details": payload.get("details"),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
def _request_args(self, email: str) -> Dict[str, Any]:
|
|
128
|
+
return {
|
|
129
|
+
"url": f"{self.base_url.rstrip('/')}{_VERIFY_PATH}",
|
|
130
|
+
"params": {"email": email},
|
|
131
|
+
"headers": {
|
|
132
|
+
"Authorization": f"Bearer {self._resolve_key()}",
|
|
133
|
+
"Accept": "application/json",
|
|
134
|
+
},
|
|
135
|
+
"timeout": self.timeout,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
def _run(
|
|
139
|
+
self,
|
|
140
|
+
email: str,
|
|
141
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
with httpx.Client() as client:
|
|
144
|
+
response = client.get(**self._request_args(email))
|
|
145
|
+
response.raise_for_status()
|
|
146
|
+
return self._shape(response.json())
|
|
147
|
+
|
|
148
|
+
async def _arun(
|
|
149
|
+
self,
|
|
150
|
+
email: str,
|
|
151
|
+
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
|
152
|
+
) -> Dict[str, Any]:
|
|
153
|
+
async with httpx.AsyncClient() as client:
|
|
154
|
+
response = await client.get(**self._request_args(email))
|
|
155
|
+
response.raise_for_status()
|
|
156
|
+
return self._shape(response.json())
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-verifly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An integration package connecting Verifly (agent-native email verification) and LangChain
|
|
5
|
+
Author-email: Verifly <support@verifly.email>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://verifly.email
|
|
8
|
+
Project-URL: Repository, https://github.com/james-sib/langchain-verifly
|
|
9
|
+
Project-URL: Source Code, https://github.com/james-sib/langchain-verifly/tree/master/langchain_verifly
|
|
10
|
+
Keywords: langchain,verifly,email-verification,email-validation,deliverability,agents,tools
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: langchain-core<0.4,>=0.3
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7.4; extra == "test"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# langchain-verifly
|
|
32
|
+
|
|
33
|
+
LangChain integration for [Verifly](https://verifly.email) — **agent-native email verification**.
|
|
34
|
+
|
|
35
|
+
`langchain-verifly` gives any LangChain agent a single, well-described tool to
|
|
36
|
+
check whether an email address is real and deliverable *before* it sends a
|
|
37
|
+
message or accepts a signup. It wraps the Verifly verification API and returns a
|
|
38
|
+
clean, structured verdict (deliverable / undeliverable / risky), the reason, a
|
|
39
|
+
confidence score, and an actionable send-or-reject recommendation.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -U langchain-verifly
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Setup
|
|
48
|
+
|
|
49
|
+
Set your Verifly API key in the environment. You can self-onboard for a key
|
|
50
|
+
(with free credits) at [verifly.email](https://verifly.email).
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export VERIFLY_API_KEY="vf_..."
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tool: `VeriflyEmailVerifier`
|
|
57
|
+
|
|
58
|
+
A LangChain `BaseTool` with both sync and async support.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
62
|
+
|
|
63
|
+
verifier = VeriflyEmailVerifier() # reads VERIFLY_API_KEY from the environment
|
|
64
|
+
|
|
65
|
+
verifier.invoke({"email": "lead@example.com"})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
{
|
|
70
|
+
"email": "lead@example.com",
|
|
71
|
+
"result": "deliverable",
|
|
72
|
+
"reason": "Mailbox exists",
|
|
73
|
+
"confidence": 95,
|
|
74
|
+
"is_valid": True,
|
|
75
|
+
"recommendation": "send",
|
|
76
|
+
"did_you_mean": None,
|
|
77
|
+
"details": {
|
|
78
|
+
"syntax_valid": True,
|
|
79
|
+
"domain_exists": True,
|
|
80
|
+
"mx_records": True,
|
|
81
|
+
"smtp_valid": True,
|
|
82
|
+
"is_disposable": False,
|
|
83
|
+
"is_role_account": False,
|
|
84
|
+
"is_catch_all": False,
|
|
85
|
+
"is_free_provider": False,
|
|
86
|
+
"provider": "example.com",
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Async works too:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
await verifier.ainvoke({"email": "lead@example.com"})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
You can also pass the key explicitly instead of using the environment variable:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
verifier = VeriflyEmailVerifier(api_key="vf_...")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Use it in an agent
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from langchain.agents import create_react_agent # or any agent constructor
|
|
107
|
+
from langchain_verifly import VeriflyEmailVerifier
|
|
108
|
+
|
|
109
|
+
tools = [VeriflyEmailVerifier()]
|
|
110
|
+
# ... wire `tools` into your agent / model.bind_tools(tools) as usual
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The agent can now verify an address on its own — for example, scrubbing a lead
|
|
114
|
+
list, validating a user-supplied email at signup, or confirming a contact
|
|
115
|
+
before drafting an outreach email.
|
|
116
|
+
|
|
117
|
+
## Verdict fields
|
|
118
|
+
|
|
119
|
+
| Field | Meaning |
|
|
120
|
+
|-------|---------|
|
|
121
|
+
| `result` | `deliverable`, `undeliverable`, or `risky` |
|
|
122
|
+
| `reason` | Human-readable explanation of the verdict |
|
|
123
|
+
| `confidence` | Confidence score, 0–100 |
|
|
124
|
+
| `is_valid` | Whether the address is considered valid/usable |
|
|
125
|
+
| `recommendation` | `send`, `do_not_send`, or `risky` |
|
|
126
|
+
| `did_you_mean` | A suggested correction for likely typos, if any |
|
|
127
|
+
| `details` | Syntax / domain / MX / SMTP / disposable / role / catch-all / free-provider flags |
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
langchain_verifly/__init__.py
|
|
5
|
+
langchain_verifly/py.typed
|
|
6
|
+
langchain_verifly/tool.py
|
|
7
|
+
langchain_verifly.egg-info/PKG-INFO
|
|
8
|
+
langchain_verifly.egg-info/SOURCES.txt
|
|
9
|
+
langchain_verifly.egg-info/dependency_links.txt
|
|
10
|
+
langchain_verifly.egg-info/requires.txt
|
|
11
|
+
langchain_verifly.egg-info/top_level.txt
|
|
12
|
+
tests/test_tool.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
langchain_verifly
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "langchain-verifly"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An integration package connecting Verifly (agent-native email verification) and LangChain"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Verifly", email = "support@verifly.email" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"langchain",
|
|
15
|
+
"verifly",
|
|
16
|
+
"email-verification",
|
|
17
|
+
"email-validation",
|
|
18
|
+
"deliverability",
|
|
19
|
+
"agents",
|
|
20
|
+
"tools",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Communications :: Email",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"langchain-core>=0.3,<0.4",
|
|
36
|
+
"httpx>=0.27",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://verifly.email"
|
|
41
|
+
Repository = "https://github.com/james-sib/langchain-verifly"
|
|
42
|
+
"Source Code" = "https://github.com/james-sib/langchain-verifly/tree/master/langchain_verifly"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = [
|
|
46
|
+
"pytest>=7.4",
|
|
47
|
+
"pytest-asyncio>=0.23",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools]
|
|
51
|
+
packages = ["langchain_verifly"]
|
|
52
|
+
|
|
53
|
+
[tool.setuptools.package-data]
|
|
54
|
+
langchain_verifly = ["py.typed"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Unit tests for VeriflyEmailVerifier (no network calls)."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from langchain_core.tools import BaseTool
|
|
7
|
+
|
|
8
|
+
from langchain_verifly import VeriflyEmailVerifier, VeriflyToolInput
|
|
9
|
+
|
|
10
|
+
_SAMPLE_RESPONSE = {
|
|
11
|
+
"success": True,
|
|
12
|
+
"email": "lead@example.com",
|
|
13
|
+
"is_valid": True,
|
|
14
|
+
"result": "deliverable",
|
|
15
|
+
"reason": "Mailbox exists",
|
|
16
|
+
"confidence": 95,
|
|
17
|
+
"recommendation": "send",
|
|
18
|
+
"did_you_mean": None,
|
|
19
|
+
"details": {
|
|
20
|
+
"syntax_valid": True,
|
|
21
|
+
"domain_exists": True,
|
|
22
|
+
"mx_records": True,
|
|
23
|
+
"smtp_valid": True,
|
|
24
|
+
"is_disposable": False,
|
|
25
|
+
"is_role_account": False,
|
|
26
|
+
"is_catch_all": False,
|
|
27
|
+
"is_free_provider": False,
|
|
28
|
+
"provider": "example.com",
|
|
29
|
+
},
|
|
30
|
+
"credits": {"used": 1, "remaining": 99},
|
|
31
|
+
"request_id": "abc-123",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _mock_transport() -> httpx.MockTransport:
|
|
36
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
37
|
+
assert request.url.path == "/api/v1/verify"
|
|
38
|
+
assert request.url.params.get("email") == "lead@example.com"
|
|
39
|
+
assert request.headers["Authorization"] == "Bearer vf_test"
|
|
40
|
+
return httpx.Response(200, json=_SAMPLE_RESPONSE)
|
|
41
|
+
|
|
42
|
+
return httpx.MockTransport(handler)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_is_a_langchain_tool():
|
|
46
|
+
tool = VeriflyEmailVerifier(api_key="vf_test")
|
|
47
|
+
assert isinstance(tool, BaseTool)
|
|
48
|
+
assert tool.name == "verifly_email_verifier"
|
|
49
|
+
assert tool.args_schema is VeriflyToolInput
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_input_schema_requires_email():
|
|
53
|
+
with pytest.raises(Exception):
|
|
54
|
+
VeriflyToolInput() # type: ignore[call-arg]
|
|
55
|
+
assert VeriflyToolInput(email="a@b.com").email == "a@b.com"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_missing_key_raises(monkeypatch):
|
|
59
|
+
monkeypatch.delenv("VERIFLY_API_KEY", raising=False)
|
|
60
|
+
tool = VeriflyEmailVerifier()
|
|
61
|
+
with pytest.raises(ValueError, match="VERIFLY_API_KEY"):
|
|
62
|
+
tool.invoke({"email": "lead@example.com"})
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_run_shapes_verdict(monkeypatch):
|
|
66
|
+
tool = VeriflyEmailVerifier(api_key="vf_test")
|
|
67
|
+
real_client = httpx.Client
|
|
68
|
+
|
|
69
|
+
def patched_client(*args, **kwargs):
|
|
70
|
+
kwargs["transport"] = _mock_transport()
|
|
71
|
+
return real_client(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
monkeypatch.setattr(httpx, "Client", patched_client)
|
|
74
|
+
|
|
75
|
+
result = tool.invoke({"email": "lead@example.com"})
|
|
76
|
+
assert result["result"] == "deliverable"
|
|
77
|
+
assert result["reason"] == "Mailbox exists"
|
|
78
|
+
assert result["confidence"] == 95
|
|
79
|
+
assert result["is_valid"] is True
|
|
80
|
+
assert result["recommendation"] == "send"
|
|
81
|
+
assert result["details"]["provider"] == "example.com"
|
|
82
|
+
# raw-only fields should be stripped out
|
|
83
|
+
assert "credits" not in result
|
|
84
|
+
assert "request_id" not in result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_key_from_env(monkeypatch):
|
|
88
|
+
monkeypatch.setenv("VERIFLY_API_KEY", "vf_test")
|
|
89
|
+
tool = VeriflyEmailVerifier()
|
|
90
|
+
real_client = httpx.Client
|
|
91
|
+
|
|
92
|
+
def patched_client(*args, **kwargs):
|
|
93
|
+
kwargs["transport"] = _mock_transport()
|
|
94
|
+
return real_client(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
monkeypatch.setattr(httpx, "Client", patched_client)
|
|
97
|
+
result = tool.invoke({"email": "lead@example.com"})
|
|
98
|
+
assert result["email"] == "lead@example.com"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_arun_shapes_verdict(monkeypatch):
|
|
103
|
+
tool = VeriflyEmailVerifier(api_key="vf_test")
|
|
104
|
+
real_async_client = httpx.AsyncClient
|
|
105
|
+
|
|
106
|
+
def patched_async_client(*args, **kwargs):
|
|
107
|
+
kwargs["transport"] = _mock_transport()
|
|
108
|
+
return real_async_client(*args, **kwargs)
|
|
109
|
+
|
|
110
|
+
monkeypatch.setattr(httpx, "AsyncClient", patched_async_client)
|
|
111
|
+
|
|
112
|
+
result = await tool.ainvoke({"email": "lead@example.com"})
|
|
113
|
+
assert result["result"] == "deliverable"
|
|
114
|
+
assert result["confidence"] == 95
|