···
890
-
(** TODO:claude - Need to implement API functions for interacting with the
891
-
mail-specific JMAP server endpoints. These would use the Jmap.Api module
892
-
to make HTTP requests and parse responses.
894
+
(** Authentication credentials for a JMAP server *)
895
+
type credentials = {
900
+
(** Connection to a JMAP mail server *)
901
+
type connection = {
902
+
session: Jmap.Types.session;
903
+
config: Jmap.Api.config;
906
+
(** Convert JSON mail object to OCaml type *)
907
+
let mailbox_of_json json =
909
+
let open Ezjsonm in
910
+
let id = get_string (find json ["id"]) in
911
+
let name = get_string (find json ["name"]) in
912
+
let parent_id = find_opt json ["parentId"] |> Option.map get_string in
913
+
let role = find_opt json ["role"] |> Option.map (fun r -> Json.mailbox_role_of_string (get_string r)) in
914
+
let sort_order = get_int (find json ["sortOrder"]) in
915
+
let total_emails = get_int (find json ["totalEmails"]) in
916
+
let unread_emails = get_int (find json ["unreadEmails"]) in
917
+
let total_threads = get_int (find json ["totalThreads"]) in
918
+
let unread_threads = get_int (find json ["unreadThreads"]) in
919
+
let is_subscribed = get_bool (find json ["isSubscribed"]) in
894
-
For a complete implementation, we would need functions for:
895
-
- Mailbox operations (get, query, changes, update)
896
-
- Thread operations
897
-
- Email operations (get, query, changes, update, import, copy)
898
-
- Search operations
900
-
- Identity management
901
-
- Vacation response management
921
+
let rights_json = find json ["myRights"] in
923
+
Types.may_read_items = get_bool (find rights_json ["mayReadItems"]);
924
+
may_add_items = get_bool (find rights_json ["mayAddItems"]);
925
+
may_remove_items = get_bool (find rights_json ["mayRemoveItems"]);
926
+
may_set_seen = get_bool (find rights_json ["maySetSeen"]);
927
+
may_set_keywords = get_bool (find rights_json ["maySetKeywords"]);
928
+
may_create_child = get_bool (find rights_json ["mayCreateChild"]);
929
+
may_rename = get_bool (find rights_json ["mayRename"]);
930
+
may_delete = get_bool (find rights_json ["mayDelete"]);
931
+
may_submit = get_bool (find rights_json ["maySubmit"]);
948
+
| Not_found -> Error (Parse_error "Required field not found in mailbox object")
949
+
| Invalid_argument msg -> Error (Parse_error msg)
950
+
| e -> Error (Parse_error (Printexc.to_string e))
952
+
(** Convert JSON email object to OCaml type *)
953
+
let email_of_json json =
955
+
let open Ezjsonm in
956
+
let id = get_string (find json ["id"]) in
957
+
let blob_id = get_string (find json ["blobId"]) in
958
+
let thread_id = get_string (find json ["threadId"]) in
960
+
(* Process mailboxIds map *)
961
+
let mailbox_ids_json = find json ["mailboxIds"] in
962
+
let mailbox_ids = match mailbox_ids_json with
963
+
| `O items -> List.map (fun (id, v) -> (id, get_bool v)) items
964
+
| _ -> raise (Invalid_argument "mailboxIds is not an object")
967
+
(* Process keywords map *)
968
+
let keywords_json = find json ["keywords"] in
969
+
let keywords = match keywords_json with
970
+
| `O items -> List.map (fun (k, v) ->
971
+
(Json.keyword_of_string k, get_bool v)) items
972
+
| _ -> raise (Invalid_argument "keywords is not an object")
975
+
let size = get_int (find json ["size"]) in
976
+
let received_at = get_string (find json ["receivedAt"]) in
977
+
let message_id = match find json ["messageId"] with
978
+
| `A ids -> List.map (fun id -> get_string id) ids
979
+
| _ -> raise (Invalid_argument "messageId is not an array")
982
+
(* Parse optional fields *)
983
+
let parse_email_addresses opt_json =
984
+
match opt_json with
985
+
| Some (`A items) ->
986
+
Some (List.map (fun addr_json ->
987
+
let name = find_opt addr_json ["name"] |> Option.map get_string in
988
+
let email = get_string (find addr_json ["email"]) in
989
+
let parameters = match find_opt addr_json ["parameters"] with
990
+
| Some (`O items) -> List.map (fun (k, v) -> (k, get_string v)) items
993
+
{ Types.name; email; parameters }
998
+
let in_reply_to = find_opt json ["inReplyTo"] |> Option.map (function
999
+
| `A ids -> List.map get_string ids
1003
+
let references = find_opt json ["references"] |> Option.map (function
1004
+
| `A ids -> List.map get_string ids
1008
+
let sender = parse_email_addresses (find_opt json ["sender"]) in
1009
+
let from = parse_email_addresses (find_opt json ["from"]) in
1010
+
let to_ = parse_email_addresses (find_opt json ["to"]) in
1011
+
let cc = parse_email_addresses (find_opt json ["cc"]) in
1012
+
let bcc = parse_email_addresses (find_opt json ["bcc"]) in
1013
+
let reply_to = parse_email_addresses (find_opt json ["replyTo"]) in
1015
+
let subject = find_opt json ["subject"] |> Option.map get_string in
1016
+
let sent_at = find_opt json ["sentAt"] |> Option.map get_string in
1017
+
let has_attachment = find_opt json ["hasAttachment"] |> Option.map get_bool in
1018
+
let preview = find_opt json ["preview"] |> Option.map get_string in
1020
+
(* Body parts parsing would go here - omitting for brevity *)
1043
+
body_values = None;
1046
+
attachments = None;
1050
+
| Not_found -> Error (Parse_error "Required field not found in email object")
1051
+
| Invalid_argument msg -> Error (Parse_error msg)
1052
+
| e -> Error (Parse_error (Printexc.to_string e))
1054
+
(** Login to a JMAP server and establish a connection
1055
+
@param uri The URI of the JMAP server
1056
+
@param credentials Authentication credentials
1057
+
@return A connection object if successful
1060
+
let login ~uri ~credentials =
1061
+
let* session_result = get_session (Uri.of_string uri)
1062
+
~username:credentials.username
1063
+
~authentication_token:credentials.password
1065
+
match session_result with
1067
+
let api_uri = Uri.of_string session.api_url in
1070
+
username = credentials.username;
1071
+
authentication_token = credentials.password;
1073
+
Lwt.return (Ok { session; config })
1074
+
| Error e -> Lwt.return (Error e)
1076
+
(** Get all mailboxes for an account
1077
+
@param conn The JMAP connection
1078
+
@param account_id The account ID to get mailboxes for
1079
+
@return A list of mailboxes if successful
1082
+
let get_mailboxes conn ~account_id =
1084
+
using = ["urn:ietf:params:jmap:core"; Types.capability_mail];
1087
+
name = "Mailbox/get";
1089
+
("accountId", `String account_id);
1091
+
method_call_id = "m1";
1094
+
created_ids = None;
1097
+
let* response_result = make_request conn.config request in
1098
+
match response_result with
1102
+
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
1103
+
inv.name = "Mailbox/get") response.method_responses in
1104
+
let args = method_response.arguments in
1105
+
match Ezjsonm.find_opt args ["list"] with
1106
+
| Some (`A mailbox_list) ->
1107
+
let parse_results = List.map mailbox_of_json mailbox_list in
1108
+
let (successes, failures) = List.partition Result.is_ok parse_results in
1109
+
if List.length failures > 0 then
1110
+
Error (Parse_error "Failed to parse some mailboxes")
1112
+
Ok (List.map Result.get_ok successes)
1113
+
| _ -> Error (Parse_error "Mailbox list not found in response")
1115
+
| Not_found -> Error (Parse_error "Mailbox/get method response not found")
1116
+
| e -> Error (Parse_error (Printexc.to_string e))
1119
+
| Error e -> Lwt.return (Error e)
1121
+
(** Get a specific mailbox by ID
1122
+
@param conn The JMAP connection
1123
+
@param account_id The account ID
1124
+
@param mailbox_id The mailbox ID to retrieve
1125
+
@return The mailbox if found
1128
+
let get_mailbox conn ~account_id ~mailbox_id =
1130
+
using = ["urn:ietf:params:jmap:core"; Types.capability_mail];
1133
+
name = "Mailbox/get";
1135
+
("accountId", `String account_id);
1136
+
("ids", `A [`String mailbox_id]);
1138
+
method_call_id = "m1";
1141
+
created_ids = None;
1144
+
let* response_result = make_request conn.config request in
1145
+
match response_result with
1149
+
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
1150
+
inv.name = "Mailbox/get") response.method_responses in
1151
+
let args = method_response.arguments in
1152
+
match Ezjsonm.find_opt args ["list"] with
1153
+
| Some (`A [mailbox]) -> mailbox_of_json mailbox
1154
+
| Some (`A []) -> Error (Parse_error ("Mailbox not found: " ^ mailbox_id))
1155
+
| _ -> Error (Parse_error "Expected single mailbox in response")
1157
+
| Not_found -> Error (Parse_error "Mailbox/get method response not found")
1158
+
| e -> Error (Parse_error (Printexc.to_string e))
1161
+
| Error e -> Lwt.return (Error e)
1163
+
(** Get messages in a mailbox
1164
+
@param conn The JMAP connection
1165
+
@param account_id The account ID
1166
+
@param mailbox_id The mailbox ID to get messages from
1167
+
@param limit Optional limit on number of messages to return
1168
+
@return The list of email messages if successful
1171
+
let get_messages_in_mailbox conn ~account_id ~mailbox_id ?limit () =
1172
+
(* First query the emails in the mailbox *)
1173
+
let query_request = {
1174
+
using = ["urn:ietf:params:jmap:core"; Types.capability_mail];
1177
+
name = "Email/query";
1179
+
("accountId", `String account_id);
1180
+
("filter", `O [("inMailbox", `String mailbox_id)]);
1181
+
("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
1182
+
] @ (match limit with
1183
+
| Some l -> [("limit", `Float (float_of_int l))]
1186
+
method_call_id = "q1";
1189
+
created_ids = None;
1192
+
let* query_result = make_request conn.config query_request in
1193
+
match query_result with
1194
+
| Ok query_response ->
1196
+
let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
1197
+
inv.name = "Email/query") query_response.method_responses in
1198
+
let args = query_method.arguments in
1199
+
match Ezjsonm.find_opt args ["ids"] with
1200
+
| Some (`A ids) ->
1201
+
let email_ids = List.map (function
1202
+
| `String id -> id
1203
+
| _ -> raise (Invalid_argument "Email ID is not a string")
1206
+
(* If we have IDs, fetch the actual email objects *)
1207
+
if List.length email_ids > 0 then
1208
+
let get_request = {
1209
+
using = ["urn:ietf:params:jmap:core"; Types.capability_mail];
1212
+
name = "Email/get";
1214
+
("accountId", `String account_id);
1215
+
("ids", `A (List.map (fun id -> `String id) email_ids));
1217
+
method_call_id = "g1";
1220
+
created_ids = None;
1223
+
let* get_result = make_request conn.config get_request in
1224
+
match get_result with
1225
+
| Ok get_response ->
1227
+
let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
1228
+
inv.name = "Email/get") get_response.method_responses in
1229
+
let args = get_method.arguments in
1230
+
match Ezjsonm.find_opt args ["list"] with
1231
+
| Some (`A email_list) ->
1232
+
let parse_results = List.map email_of_json email_list in
1233
+
let (successes, failures) = List.partition Result.is_ok parse_results in
1234
+
if List.length failures > 0 then
1235
+
Lwt.return (Error (Parse_error "Failed to parse some emails"))
1237
+
Lwt.return (Ok (List.map Result.get_ok successes))
1238
+
| _ -> Lwt.return (Error (Parse_error "Email list not found in response"))
1240
+
| Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found"))
1241
+
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
1242
+
| Error e -> Lwt.return (Error e)
1244
+
(* No emails in mailbox *)
1245
+
Lwt.return (Ok [])
1247
+
| _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response"))
1249
+
| Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found"))
1250
+
| Invalid_argument msg -> Lwt.return (Error (Parse_error msg))
1251
+
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
1252
+
| Error e -> Lwt.return (Error e)
1254
+
(** Get a single email message by ID
1255
+
@param conn The JMAP connection
1256
+
@param account_id The account ID
1257
+
@param email_id The email ID to retrieve
1258
+
@return The email message if found
1261
+
let get_email conn ~account_id ~email_id =
1263
+
using = ["urn:ietf:params:jmap:core"; Types.capability_mail];
1266
+
name = "Email/get";
1268
+
("accountId", `String account_id);
1269
+
("ids", `A [`String email_id]);
1271
+
method_call_id = "m1";
1274
+
created_ids = None;
1277
+
let* response_result = make_request conn.config request in
1278
+
match response_result with
1282
+
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
1283
+
inv.name = "Email/get") response.method_responses in
1284
+
let args = method_response.arguments in
1285
+
match Ezjsonm.find_opt args ["list"] with
1286
+
| Some (`A [email]) -> email_of_json email
1287
+
| Some (`A []) -> Error (Parse_error ("Email not found: " ^ email_id))
1288
+
| _ -> Error (Parse_error "Expected single email in response")
1290
+
| Not_found -> Error (Parse_error "Email/get method response not found")
1291
+
| e -> Error (Parse_error (Printexc.to_string e))
1294
+
| Error e -> Lwt.return (Error e)