dhisana 0.0.1.dev116__py3-none-any.whl → 0.0.1.dev236__py3-none-any.whl
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.
- dhisana/schemas/common.py +10 -1
- dhisana/schemas/sales.py +203 -22
- dhisana/utils/add_mapping.py +0 -2
- dhisana/utils/apollo_tools.py +739 -119
- dhisana/utils/built_with_api_tools.py +4 -2
- dhisana/utils/check_email_validity_tools.py +35 -18
- dhisana/utils/check_for_intent_signal.py +1 -2
- dhisana/utils/check_linkedin_url_validity.py +34 -8
- dhisana/utils/clay_tools.py +3 -2
- dhisana/utils/clean_properties.py +1 -4
- dhisana/utils/compose_salesnav_query.py +0 -1
- dhisana/utils/compose_search_query.py +7 -3
- dhisana/utils/composite_tools.py +0 -1
- dhisana/utils/dataframe_tools.py +2 -2
- dhisana/utils/email_body_utils.py +72 -0
- dhisana/utils/email_provider.py +174 -35
- dhisana/utils/enrich_lead_information.py +183 -53
- dhisana/utils/fetch_openai_config.py +129 -0
- dhisana/utils/field_validators.py +1 -1
- dhisana/utils/g2_tools.py +0 -1
- dhisana/utils/generate_content.py +0 -1
- dhisana/utils/generate_email.py +68 -23
- dhisana/utils/generate_email_response.py +294 -46
- dhisana/utils/generate_flow.py +0 -1
- dhisana/utils/generate_linkedin_connect_message.py +9 -2
- dhisana/utils/generate_linkedin_response_message.py +137 -66
- dhisana/utils/generate_structured_output_internal.py +317 -164
- dhisana/utils/google_custom_search.py +150 -44
- dhisana/utils/google_oauth_tools.py +721 -0
- dhisana/utils/google_workspace_tools.py +278 -54
- dhisana/utils/hubspot_clearbit.py +3 -1
- dhisana/utils/hubspot_crm_tools.py +718 -272
- dhisana/utils/instantly_tools.py +3 -1
- dhisana/utils/lusha_tools.py +10 -7
- dhisana/utils/mailgun_tools.py +150 -0
- dhisana/utils/microsoft365_tools.py +447 -0
- dhisana/utils/openai_assistant_and_file_utils.py +121 -177
- dhisana/utils/openai_helpers.py +8 -6
- dhisana/utils/parse_linkedin_messages_txt.py +1 -3
- dhisana/utils/profile.py +37 -0
- dhisana/utils/proxy_curl_tools.py +377 -76
- dhisana/utils/proxycurl_search_leads.py +426 -0
- dhisana/utils/research_lead.py +3 -3
- dhisana/utils/sales_navigator_crawler.py +1 -6
- dhisana/utils/salesforce_crm_tools.py +323 -50
- dhisana/utils/search_router.py +131 -0
- dhisana/utils/search_router_jobs.py +51 -0
- dhisana/utils/sendgrid_tools.py +126 -91
- dhisana/utils/serarch_router_local_business.py +75 -0
- dhisana/utils/serpapi_additional_tools.py +290 -0
- dhisana/utils/serpapi_google_jobs.py +117 -0
- dhisana/utils/serpapi_google_search.py +188 -0
- dhisana/utils/serpapi_local_business_search.py +129 -0
- dhisana/utils/serpapi_search_tools.py +360 -432
- dhisana/utils/serperdev_google_jobs.py +125 -0
- dhisana/utils/serperdev_local_business.py +154 -0
- dhisana/utils/serperdev_search.py +233 -0
- dhisana/utils/smtp_email_tools.py +178 -18
- dhisana/utils/test_connect.py +1603 -130
- dhisana/utils/trasform_json.py +3 -3
- dhisana/utils/web_download_parse_tools.py +0 -1
- dhisana/utils/zoominfo_tools.py +2 -3
- dhisana/workflow/test.py +1 -1
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +1 -1
- dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
- dhisana-0.0.1.dev116.dist-info/RECORD +0 -83
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
dhisana/utils/email_provider.py
CHANGED
|
@@ -6,24 +6,37 @@
|
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Any, Dict, List, Optional, Sequence
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from dhisana.schemas.common import (
|
|
10
|
+
SendEmailContext,
|
|
11
|
+
QueryEmailContext,
|
|
12
|
+
ReplyEmailContext,
|
|
13
|
+
)
|
|
12
14
|
from dhisana.schemas.sales import MessageItem
|
|
13
|
-
|
|
14
15
|
from dhisana.utils.google_workspace_tools import (
|
|
15
16
|
send_email_using_service_account_async,
|
|
16
17
|
list_emails_in_time_range_async,
|
|
17
|
-
reply_to_email_async as gw_reply_to_email_async,
|
|
18
|
+
reply_to_email_async as gw_reply_to_email_async,
|
|
19
|
+
)
|
|
20
|
+
from dhisana.utils.google_oauth_tools import (
|
|
21
|
+
send_email_using_google_oauth_async,
|
|
22
|
+
list_emails_in_time_range_google_oauth_async,
|
|
23
|
+
reply_to_email_google_oauth_async,
|
|
24
|
+
)
|
|
25
|
+
from dhisana.utils.microsoft365_tools import (
|
|
26
|
+
send_email_using_microsoft_graph_async,
|
|
27
|
+
list_emails_in_time_range_m365_async,
|
|
28
|
+
reply_to_email_m365_async,
|
|
18
29
|
)
|
|
19
30
|
from dhisana.utils.smtp_email_tools import (
|
|
20
31
|
send_email_via_smtp_async,
|
|
21
32
|
list_emails_in_time_range_imap_async,
|
|
22
|
-
reply_to_email_via_smtp_async,
|
|
33
|
+
reply_to_email_via_smtp_async,
|
|
23
34
|
)
|
|
35
|
+
from dhisana.utils.mailgun_tools import send_email_using_mailgun_async
|
|
36
|
+
from dhisana.utils.sendgrid_tools import send_email_using_sendgrid_async
|
|
24
37
|
|
|
25
38
|
# --------------------------------------------------------------------------- #
|
|
26
|
-
# Provider
|
|
39
|
+
# Provider-selection helpers
|
|
27
40
|
# --------------------------------------------------------------------------- #
|
|
28
41
|
|
|
29
42
|
|
|
@@ -31,35 +44,35 @@ def _find_provider_cfg(
|
|
|
31
44
|
tool_cfg: Optional[Sequence[Dict]], provider_name: str
|
|
32
45
|
) -> Optional[Dict]:
|
|
33
46
|
"""
|
|
34
|
-
Return the *first* config
|
|
47
|
+
Return the *first* config-dict whose ``name`` matches *provider_name*.
|
|
35
48
|
"""
|
|
36
49
|
if not tool_cfg:
|
|
37
50
|
return None
|
|
38
51
|
return next((c for c in tool_cfg if c.get("name") == provider_name), None)
|
|
39
52
|
|
|
40
53
|
|
|
41
|
-
def _smtp_creds_for_sender(
|
|
42
|
-
smtp_cfg: Dict, sender_email: str
|
|
43
|
-
) -> Optional[Dict[str, str]]:
|
|
54
|
+
def _smtp_creds_for_sender(smtp_cfg: Dict, sender_email: str) -> Optional[Dict[str, str]]:
|
|
44
55
|
"""
|
|
45
56
|
Given an SMTP provider config and a sender address, return the matching
|
|
46
|
-
``username``
|
|
57
|
+
``username`` / ``password`` plus server settings, or ``None``.
|
|
47
58
|
"""
|
|
48
59
|
try:
|
|
49
60
|
usernames = [
|
|
50
|
-
u.strip()
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
u.strip()
|
|
62
|
+
for u in next(f for f in smtp_cfg["configuration"] if f["name"] == "usernames")[
|
|
63
|
+
"value"
|
|
64
|
+
].split(",")
|
|
53
65
|
if u.strip()
|
|
54
66
|
]
|
|
55
67
|
passwords = [
|
|
56
|
-
p.strip()
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
p.strip()
|
|
69
|
+
for p in next(f for f in smtp_cfg["configuration"] if f["name"] == "passwords")[
|
|
70
|
+
"value"
|
|
71
|
+
].split(",")
|
|
59
72
|
]
|
|
60
73
|
if len(usernames) != len(passwords):
|
|
61
74
|
logging.warning(
|
|
62
|
-
"smtpEmail config: usernames/passwords length mismatch
|
|
75
|
+
"smtpEmail config: usernames/passwords length mismatch – skipping"
|
|
63
76
|
)
|
|
64
77
|
return None
|
|
65
78
|
|
|
@@ -97,17 +110,29 @@ async def send_email_async(
|
|
|
97
110
|
send_email_context: SendEmailContext,
|
|
98
111
|
tool_config: Optional[List[Dict]] = None,
|
|
99
112
|
*,
|
|
100
|
-
provider_order: Sequence[str] = (
|
|
113
|
+
provider_order: Sequence[str] = (
|
|
114
|
+
"mailgun",
|
|
115
|
+
"sendgrid",
|
|
116
|
+
"google", # Google OAuth (per-user token)
|
|
117
|
+
"smtpEmail",
|
|
118
|
+
"googleworkspace", # Google Workspace service account (DWD)
|
|
119
|
+
"microsoft365",
|
|
120
|
+
),
|
|
101
121
|
):
|
|
102
122
|
"""
|
|
103
123
|
Send an e-mail using the first *configured* provider in *provider_order*.
|
|
104
124
|
|
|
105
125
|
Returns whatever the underlying provider helper returns:
|
|
106
126
|
|
|
107
|
-
* SMTP
|
|
108
|
-
*
|
|
127
|
+
* SMTP → str (Message-ID)
|
|
128
|
+
* Microsoft 365 → str (message-id)
|
|
129
|
+
* Google Workspace → str (message-id)
|
|
109
130
|
"""
|
|
131
|
+
# ------------------------------------------------------------------ #
|
|
132
|
+
# 1) Try the preferred providers in order
|
|
133
|
+
# ------------------------------------------------------------------ #
|
|
110
134
|
for provider in provider_order:
|
|
135
|
+
# 1a) SMTP
|
|
111
136
|
if provider == "smtpEmail":
|
|
112
137
|
smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
|
|
113
138
|
if not smtp_cfg:
|
|
@@ -115,7 +140,7 @@ async def send_email_async(
|
|
|
115
140
|
|
|
116
141
|
creds = _smtp_creds_for_sender(smtp_cfg, send_email_context.sender_email)
|
|
117
142
|
if not creds:
|
|
118
|
-
# No
|
|
143
|
+
# No creds for this sender – fall through.
|
|
119
144
|
continue
|
|
120
145
|
|
|
121
146
|
return await send_email_via_smtp_async(
|
|
@@ -127,22 +152,104 @@ async def send_email_async(
|
|
|
127
152
|
use_starttls=(creds["smtp_port"] == 587),
|
|
128
153
|
)
|
|
129
154
|
|
|
155
|
+
# 1b) Mailgun
|
|
156
|
+
elif provider == "mailgun":
|
|
157
|
+
mg_cfg = _find_provider_cfg(tool_config, "mailgun")
|
|
158
|
+
if not mg_cfg:
|
|
159
|
+
continue
|
|
160
|
+
return await send_email_using_mailgun_async(send_email_context, tool_config)
|
|
161
|
+
|
|
162
|
+
# 1c) SendGrid
|
|
163
|
+
elif provider == "sendgrid":
|
|
164
|
+
sg_cfg = _find_provider_cfg(tool_config, "sendgrid")
|
|
165
|
+
if not sg_cfg:
|
|
166
|
+
continue
|
|
167
|
+
return await send_email_using_sendgrid_async(send_email_context, tool_config)
|
|
168
|
+
|
|
169
|
+
# 1d) Google (Gmail API via per-user OAuth)
|
|
170
|
+
elif provider == "google":
|
|
171
|
+
g_cfg = _find_provider_cfg(tool_config, "google")
|
|
172
|
+
if not g_cfg:
|
|
173
|
+
continue
|
|
174
|
+
return await send_email_using_google_oauth_async(send_email_context, tool_config)
|
|
175
|
+
|
|
176
|
+
# 1e) Google Workspace
|
|
130
177
|
elif provider == "googleworkspace":
|
|
131
178
|
gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
|
|
132
179
|
if not gw_cfg:
|
|
133
180
|
continue
|
|
134
|
-
return await send_email_using_service_account_async(send_email_context, tool_config)
|
|
135
181
|
|
|
136
|
-
|
|
182
|
+
return await send_email_using_service_account_async(
|
|
183
|
+
send_email_context, tool_config
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# 1f) Microsoft 365 (Graph API)
|
|
187
|
+
elif provider == "microsoft365":
|
|
188
|
+
ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
|
|
189
|
+
if not ms_cfg:
|
|
190
|
+
continue
|
|
137
191
|
|
|
192
|
+
return await send_email_using_microsoft_graph_async(
|
|
193
|
+
send_email_context, tool_config
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# -- future providers slot --------------------------------------
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------ #
|
|
199
|
+
# 2) FINAL FALLBACK — use *first* SMTP credentials if available
|
|
200
|
+
# ------------------------------------------------------------------ #
|
|
201
|
+
smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
|
|
202
|
+
if smtp_cfg:
|
|
203
|
+
try:
|
|
204
|
+
usernames = [
|
|
205
|
+
u.strip()
|
|
206
|
+
for u in next(
|
|
207
|
+
f for f in smtp_cfg["configuration"] if f["name"] == "usernames"
|
|
208
|
+
)["value"].split(",")
|
|
209
|
+
if u.strip()
|
|
210
|
+
]
|
|
211
|
+
passwords = [
|
|
212
|
+
p.strip()
|
|
213
|
+
for p in next(
|
|
214
|
+
f for f in smtp_cfg["configuration"] if f["name"] == "passwords"
|
|
215
|
+
)["value"].split(",")
|
|
216
|
+
]
|
|
217
|
+
if usernames and len(usernames) == len(passwords):
|
|
218
|
+
# Build a fake SendEmailContext for the fallback user, so that
|
|
219
|
+
# the underlying SMTP helper still sends the intended message
|
|
220
|
+
# but authenticates with the first available mailbox.
|
|
221
|
+
fallback_sender = usernames[0]
|
|
222
|
+
creds = _smtp_creds_for_sender(smtp_cfg, fallback_sender)
|
|
223
|
+
|
|
224
|
+
if creds:
|
|
225
|
+
logging.info(
|
|
226
|
+
"Fallback: no provider matched – using first SMTP creds (%s).",
|
|
227
|
+
creds["username"],
|
|
228
|
+
)
|
|
229
|
+
return await send_email_via_smtp_async(
|
|
230
|
+
send_email_context,
|
|
231
|
+
smtp_server=creds["smtp_host"],
|
|
232
|
+
smtp_port=creds["smtp_port"],
|
|
233
|
+
username=creds["username"],
|
|
234
|
+
password=creds["password"],
|
|
235
|
+
use_starttls=(creds["smtp_port"] == 587),
|
|
236
|
+
)
|
|
237
|
+
except Exception:
|
|
238
|
+
logging.exception("SMTP fallback failed")
|
|
239
|
+
|
|
240
|
+
# ------------------------------------------------------------------ #
|
|
241
|
+
# 3) Nothing worked
|
|
242
|
+
# ------------------------------------------------------------------ #
|
|
138
243
|
raise RuntimeError("No suitable e-mail provider configured for this sender.")
|
|
139
244
|
|
|
140
245
|
|
|
246
|
+
|
|
247
|
+
|
|
141
248
|
async def list_emails_async(
|
|
142
249
|
query_email_context: QueryEmailContext,
|
|
143
250
|
tool_config: Optional[List[Dict]] = None,
|
|
144
251
|
*,
|
|
145
|
-
provider_order: Sequence[str] = ("smtpEmail", "googleworkspace"),
|
|
252
|
+
provider_order: Sequence[str] = ("google", "smtpEmail", "googleworkspace", "microsoft365"),
|
|
146
253
|
) -> List[MessageItem]:
|
|
147
254
|
"""
|
|
148
255
|
List e-mails (see ``QueryEmailContext``) using the first configured provider.
|
|
@@ -167,17 +274,31 @@ async def list_emails_async(
|
|
|
167
274
|
password=creds["password"],
|
|
168
275
|
)
|
|
169
276
|
|
|
277
|
+
elif provider == "google":
|
|
278
|
+
g_cfg = _find_provider_cfg(tool_config, "google")
|
|
279
|
+
if not g_cfg:
|
|
280
|
+
continue
|
|
281
|
+
return await list_emails_in_time_range_google_oauth_async(query_email_context, tool_config)
|
|
282
|
+
|
|
170
283
|
elif provider == "googleworkspace":
|
|
171
284
|
gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
|
|
172
285
|
if not gw_cfg:
|
|
173
286
|
continue
|
|
174
287
|
return await list_emails_in_time_range_async(query_email_context, tool_config)
|
|
175
288
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
289
|
+
elif provider == "microsoft365":
|
|
290
|
+
ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
|
|
291
|
+
if not ms_cfg:
|
|
292
|
+
continue
|
|
293
|
+
return await list_emails_in_time_range_m365_async(query_email_context, tool_config)
|
|
179
294
|
|
|
295
|
+
# --- future providers go here ---
|
|
180
296
|
|
|
297
|
+
logging.warning(
|
|
298
|
+
"No suitable inbox provider configured for sender %s; returning empty list.",
|
|
299
|
+
query_email_context.sender_email,
|
|
300
|
+
)
|
|
301
|
+
return []
|
|
181
302
|
|
|
182
303
|
|
|
183
304
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -187,7 +308,7 @@ async def reply_email_async(
|
|
|
187
308
|
reply_email_context: ReplyEmailContext,
|
|
188
309
|
tool_config: Optional[List[Dict]] = None,
|
|
189
310
|
*,
|
|
190
|
-
provider_order: Sequence[str] = ("smtpEmail", "googleworkspace"),
|
|
311
|
+
provider_order: Sequence[str] = ("google", "smtpEmail", "googleworkspace", "microsoft365"),
|
|
191
312
|
) -> Dict[str, Any]:
|
|
192
313
|
"""
|
|
193
314
|
Reply (reply-all) to an e-mail using the first *configured* provider
|
|
@@ -197,16 +318,14 @@ async def reply_email_async(
|
|
|
197
318
|
"""
|
|
198
319
|
for provider in provider_order:
|
|
199
320
|
# ------------------------------------------------------------------
|
|
200
|
-
# 1) SMTP
|
|
321
|
+
# 1) SMTP
|
|
201
322
|
# ------------------------------------------------------------------
|
|
202
323
|
if provider == "smtpEmail":
|
|
203
324
|
smtp_cfg = _find_provider_cfg(tool_config, "smtpEmail")
|
|
204
325
|
if not smtp_cfg:
|
|
205
326
|
continue
|
|
206
327
|
|
|
207
|
-
creds = _smtp_creds_for_sender(
|
|
208
|
-
smtp_cfg, reply_email_context.sender_email
|
|
209
|
-
)
|
|
328
|
+
creds = _smtp_creds_for_sender(smtp_cfg, reply_email_context.sender_email)
|
|
210
329
|
if not creds:
|
|
211
330
|
continue
|
|
212
331
|
|
|
@@ -222,7 +341,17 @@ async def reply_email_async(
|
|
|
222
341
|
)
|
|
223
342
|
|
|
224
343
|
# ------------------------------------------------------------------
|
|
225
|
-
# 2) Google
|
|
344
|
+
# 2) Google OAuth (per-user)
|
|
345
|
+
# ------------------------------------------------------------------
|
|
346
|
+
elif provider == "google":
|
|
347
|
+
g_cfg = _find_provider_cfg(tool_config, "google")
|
|
348
|
+
if not g_cfg:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
return await reply_to_email_google_oauth_async(reply_email_context, tool_config)
|
|
352
|
+
|
|
353
|
+
# ------------------------------------------------------------------
|
|
354
|
+
# 3) Google Workspace service-account
|
|
226
355
|
# ------------------------------------------------------------------
|
|
227
356
|
elif provider == "googleworkspace":
|
|
228
357
|
gw_cfg = _find_provider_cfg(tool_config, "googleworkspace")
|
|
@@ -231,6 +360,16 @@ async def reply_email_async(
|
|
|
231
360
|
|
|
232
361
|
return await gw_reply_to_email_async(reply_email_context, tool_config)
|
|
233
362
|
|
|
363
|
+
# ------------------------------------------------------------------
|
|
364
|
+
# 4) Microsoft 365 (Graph)
|
|
365
|
+
# ------------------------------------------------------------------
|
|
366
|
+
elif provider == "microsoft365":
|
|
367
|
+
ms_cfg = _find_provider_cfg(tool_config, "microsoft365")
|
|
368
|
+
if not ms_cfg:
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
return await reply_to_email_m365_async(reply_email_context, tool_config)
|
|
372
|
+
|
|
234
373
|
# -- future providers slot -----------------------------------------
|
|
235
374
|
|
|
236
375
|
raise RuntimeError("No suitable reply-capable e-mail provider configured.")
|