human handoffs
This commit is contained in:
parent
79188e0f59
commit
36a55930f9
35 changed files with 4039 additions and 139 deletions
|
|
@ -9,8 +9,18 @@ from sqlalchemy import func, select
|
|||
from app.config import get_settings
|
||||
from app.database import SessionFactory, mongo_db
|
||||
from app.models import Account
|
||||
from app.services.domain_provisioning import get_domain_by_name
|
||||
from app.services.human_handoff_service import (
|
||||
MESSAGE_SCOPE_EXTERNAL,
|
||||
MESSAGE_SCOPE_HANDOFF,
|
||||
process_human_reply,
|
||||
resolve_handoff_from_inbound,
|
||||
maybe_trigger_handoff,
|
||||
)
|
||||
from app.services.mime_parser import parse_mime
|
||||
from app.services.raw_mime_storage import delivery_digest, get_raw_mime_storage
|
||||
from app.services.thread_resolver import participants_from_parsed, resolve_thread
|
||||
from app.services.thread_service import ensure_thread_for_message, message_event_at
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -69,9 +79,59 @@ class InboundHandler:
|
|||
body_html = parsed.pop("body_html")
|
||||
stored = await get_raw_mime_storage().put(raw, delivery_key)
|
||||
now = datetime.now(timezone.utc)
|
||||
sender_address = (parsed.get("sender") or {}).get("address", "").lower()
|
||||
|
||||
for recipient in recipients:
|
||||
account_id = envelope.account_ids[recipient]
|
||||
agent_email = recipient
|
||||
|
||||
handoff = await resolve_handoff_from_inbound(
|
||||
mongo_db,
|
||||
account_id=account_id,
|
||||
agent_email=agent_email,
|
||||
sender_address=sender_address,
|
||||
in_reply_to=parsed.get("in_reply_to"),
|
||||
references=parsed.get("references", []),
|
||||
)
|
||||
if handoff:
|
||||
async with SessionFactory() as database_session:
|
||||
domain = await get_domain_by_name(
|
||||
database_session,
|
||||
agent_email.split("@", 1)[1],
|
||||
)
|
||||
if domain is None:
|
||||
logger.warning("handoff_reply_no_domain account=%s", account_id)
|
||||
continue
|
||||
message = {
|
||||
**parsed,
|
||||
"body_text": body_text,
|
||||
"body_html": body_html,
|
||||
"direction": "inbound",
|
||||
"message_scope": MESSAGE_SCOPE_HANDOFF,
|
||||
"delivery_key": delivery_key,
|
||||
"received_at": now,
|
||||
"created_at": now,
|
||||
}
|
||||
await process_human_reply(
|
||||
mongo_db,
|
||||
account_id=account_id,
|
||||
agent_email=agent_email,
|
||||
handoff=handoff,
|
||||
inbound_parsed=message,
|
||||
inbound_body=body_text,
|
||||
domain=domain,
|
||||
)
|
||||
logger.info(
|
||||
"handoff_human_reply handoff_id=%s account=%s",
|
||||
handoff["_id"],
|
||||
account_id,
|
||||
)
|
||||
return f"250 2.0.0 Handoff reply accepted as {delivery_key}"
|
||||
|
||||
message = {
|
||||
**parsed,
|
||||
"direction": "inbound",
|
||||
"message_scope": MESSAGE_SCOPE_EXTERNAL,
|
||||
"envelope_sender": envelope.mail_from.lower(),
|
||||
"envelope_recipients": recipients,
|
||||
"delivery_key": delivery_key,
|
||||
|
|
@ -92,14 +152,54 @@ class InboundHandler:
|
|||
{"$setOnInsert": message},
|
||||
upsert=True,
|
||||
)
|
||||
persisted = await mongo_db.messages.find_one(
|
||||
{"delivery_key": delivery_key},
|
||||
{"_id": 1},
|
||||
)
|
||||
persisted = await mongo_db.messages.find_one({"delivery_key": delivery_key})
|
||||
if persisted is None:
|
||||
raise RuntimeError("Inbound message was not persisted.")
|
||||
|
||||
if body_text:
|
||||
await mongo_db.messages.update_one(
|
||||
{"_id": persisted["_id"]},
|
||||
{"$set": {"body_text": body_text, "body_html": body_html}},
|
||||
)
|
||||
persisted["body_text"] = body_text
|
||||
persisted["body_html"] = body_html
|
||||
|
||||
participants = participants_from_parsed(parsed)
|
||||
for recipient in recipients:
|
||||
account_id = envelope.account_ids[recipient]
|
||||
agent_email = recipient
|
||||
resolution = await resolve_thread(
|
||||
mongo_db,
|
||||
account_id=account_id,
|
||||
in_reply_to=parsed.get("in_reply_to"),
|
||||
references=parsed.get("references", []),
|
||||
subject=parsed.get("subject", ""),
|
||||
participants=participants,
|
||||
)
|
||||
thread_id = await ensure_thread_for_message(
|
||||
mongo_db,
|
||||
account_id=account_id,
|
||||
message=persisted,
|
||||
resolution=resolution,
|
||||
)
|
||||
await mongo_db.email_threads.update_one(
|
||||
{"_id": thread_id},
|
||||
{"$set": {"thread_type": "EXTERNAL"}},
|
||||
)
|
||||
await mongo_db.messages.update_one(
|
||||
{"_id": persisted["_id"]},
|
||||
{
|
||||
"$set": {
|
||||
"message_scope": MESSAGE_SCOPE_EXTERNAL,
|
||||
"in_reply_to": parsed.get("in_reply_to"),
|
||||
"references": parsed.get("references", []),
|
||||
"raw_headers": parsed.get("raw_headers", {}),
|
||||
"parent_message_id": resolution.parent_message_id,
|
||||
"thread_relation": resolution.thread_relation,
|
||||
"thread_resolution_method": resolution.thread_resolution_method,
|
||||
}
|
||||
},
|
||||
)
|
||||
await mongo_db.mailbox_entries.update_one(
|
||||
{
|
||||
"account_id": account_id,
|
||||
|
|
@ -111,6 +211,7 @@ class InboundHandler:
|
|||
"account_id": account_id,
|
||||
"message_id": persisted["_id"],
|
||||
"mailbox": "inbox",
|
||||
"message_scope": MESSAGE_SCOPE_EXTERNAL,
|
||||
"direction": "inbound",
|
||||
"sender": parsed["sender"],
|
||||
"recipients": parsed["to"],
|
||||
|
|
@ -120,14 +221,34 @@ class InboundHandler:
|
|||
"status": "received",
|
||||
"is_read": False,
|
||||
"is_starred": False,
|
||||
"event_at": now,
|
||||
"event_at": message_event_at(persisted),
|
||||
"created_at": now,
|
||||
"trashed_at": None,
|
||||
"deletion_state": None,
|
||||
}
|
||||
},
|
||||
"$set": {"thread_id": thread_id},
|
||||
},
|
||||
upsert=True,
|
||||
)
|
||||
async with SessionFactory() as database_session:
|
||||
domain = await get_domain_by_name(
|
||||
database_session,
|
||||
agent_email.split("@", 1)[1],
|
||||
)
|
||||
if domain is not None:
|
||||
await maybe_trigger_handoff(
|
||||
mongo_db,
|
||||
account_id=account_id,
|
||||
agent_email=agent_email,
|
||||
external_message=persisted,
|
||||
external_thread_id=thread_id,
|
||||
domain=domain,
|
||||
)
|
||||
logger.info(
|
||||
"message_received message_id=%s thread_id=%s scope=EXTERNAL",
|
||||
persisted["_id"],
|
||||
thread_id,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Temporary failure accepting inbound message")
|
||||
return "451 4.3.0 Temporary server failure; please retry"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue