My agentic slop goes here. Not intended for anyone else!
1(** Simple JMAP client test against Fastmail API *) 2 3let read_api_key () = 4 let locations = [ 5 "jmap/.api-key"; 6 "../jmap/.api-key"; 7 "../../jmap/.api-key"; 8 ".api-key"; 9 ] in 10 11 let rec try_read = function 12 | [] -> 13 Printf.eprintf "Error: API key file not found. Checked:\n"; 14 List.iter (fun loc -> Printf.eprintf " - %s\n" loc) locations; 15 Printf.eprintf "\nCreate .api-key with your Fastmail API token.\n"; 16 Printf.eprintf "Get one at: https://www.fastmail.com/settings/security/tokens\n"; 17 exit 1 18 | path :: rest -> 19 if Sys.file_exists path then 20 let ic = open_in path in 21 Fun.protect ~finally:(fun () -> close_in ic) (fun () -> 22 let token = input_line ic |> String.trim in 23 if token = "" then ( 24 Printf.eprintf "Error: API key file is empty: %s\n" path; 25 exit 1 26 ); 27 token 28 ) 29 else 30 try_read rest 31 in 32 try_read locations 33 34let () = 35 let () = Mirage_crypto_rng_unix.use_default () in 36 37 Eio_main.run @@ fun env -> 38 Eio.Switch.run @@ fun sw -> 39 40 Printf.printf "=== JMAP Fastmail Test ===\n\n%!"; 41 42 Printf.printf "Reading API key...\n%!"; 43 let api_key = read_api_key () in 44 Printf.printf "✓ API key loaded\n\n%!"; 45 46 let conn = Jmap_connection.v 47 ~auth:(Jmap_connection.Bearer api_key) 48 () in 49 50 let session_url = "https://api.fastmail.com/jmap/session" in 51 Printf.printf "Connecting to %s...\n%!" session_url; 52 53 let client = Jmap_client.create ~sw ~env ~conn ~session_url () in 54 55 Printf.printf "Fetching JMAP session...\n%!"; 56 let session = Jmap_client.fetch_session client in 57 Printf.printf "✓ Session fetched\n"; 58 Printf.printf " Username: %s\n" (Jmap_core.Session.username session); 59 Printf.printf " API URL: %s\n\n%!" (Jmap_core.Session.api_url session); 60 61 (* Get primary mail account *) 62 let primary_accounts = Jmap_core.Session.primary_accounts session in 63 let account_id = match List.assoc_opt "urn:ietf:params:jmap:mail" primary_accounts with 64 | Some id -> Jmap_core.Id.to_string id 65 | None -> 66 Printf.eprintf "Error: No mail account found\n"; 67 exit 1 68 in 69 Printf.printf " Account ID: %s\n\n%!" account_id; 70 71 (* Build a JMAP request using the typed library API *) 72 Printf.printf "Querying for 10 most recent emails...\n"; 73 Printf.printf " API URL: %s\n%!" (Jmap_core.Session.api_url session); 74 75 (* Build Email/query request using typed constructors *) 76 let query_request = Jmap_mail.Email.Query.request_v 77 ~account_id:(Jmap_core.Id.of_string account_id) 78 ~limit:(Jmap_core.Primitives.UnsignedInt.of_int 10) 79 ~sort:[Jmap_core.Comparator.v ~property:"receivedAt" ~is_ascending:false ()] 80 ~calculate_total:true 81 () in 82 83 (* Convert to JSON *) 84 let query_args = Jmap_mail.Email.Query.request_to_json query_request in 85 86 (* Create invocation using Echo witness *) 87 let query_invocation = Jmap_core.Invocation.Invocation { 88 method_name = "Email/query"; 89 arguments = query_args; 90 call_id = "q1"; 91 witness = Jmap_core.Invocation.Echo; 92 } in 93 94 (* Build request using constructors *) 95 let req = Jmap_core.Request.make 96 ~using:[Jmap_core.Capability.core; Jmap_core.Capability.mail] 97 [Jmap_core.Invocation.Packed query_invocation] 98 in 99 100 Printf.printf " Request built using typed Email.Query API\n%!"; 101 102 Printf.printf " Making API call...\n%!"; 103 (try 104 let query_resp = Jmap_client.call client req in 105 Printf.printf "✓ Query successful!\n"; 106 107 (* Extract email IDs from the query response *) 108 let method_responses = Jmap_core.Response.method_responses query_resp in 109 let email_ids = match method_responses with 110 | [packed_resp] -> 111 let response_json = Jmap_core.Invocation.response_to_json packed_resp in 112 (match response_json with 113 | `O fields -> 114 (match List.assoc_opt "ids" fields with 115 | Some (`A ids) -> 116 List.map (fun id -> 117 match id with 118 | `String s -> Jmap_core.Id.of_string s 119 | _ -> failwith "Expected string ID" 120 ) ids 121 | _ -> failwith "No 'ids' field in query response") 122 | _ -> failwith "Expected object response") 123 | _ -> failwith "Unexpected response structure" 124 in 125 126 Printf.printf " Found %d email(s)\n\n%!" (List.length email_ids); 127 128 if List.length email_ids > 0 then ( 129 (* Fetch the actual emails with Email/get *) 130 let get_request = Jmap_mail.Email.Get.request_v 131 ~account_id:(Jmap_core.Id.of_string account_id) 132 ~ids:email_ids 133 ~properties:["id"; "subject"; "from"; "receivedAt"] 134 () in 135 136 let get_args = Jmap_mail.Email.Get.request_to_json get_request in 137 138 let get_invocation = Jmap_core.Invocation.Invocation { 139 method_name = "Email/get"; 140 arguments = get_args; 141 call_id = "g1"; 142 witness = Jmap_core.Invocation.Echo; 143 } in 144 145 let get_req = Jmap_core.Request.make 146 ~using:[Jmap_core.Capability.core; Jmap_core.Capability.mail] 147 [Jmap_core.Invocation.Packed get_invocation] 148 in 149 150 let get_resp = Jmap_client.call client get_req in 151 152 (* Parse and display emails *) 153 let get_method_responses = Jmap_core.Response.method_responses get_resp in 154 (match get_method_responses with 155 | [packed_resp] -> 156 let response_json = Jmap_core.Invocation.response_to_json packed_resp in 157 (match response_json with 158 | `O fields -> 159 (match List.assoc_opt "list" fields with 160 | Some (`A emails) -> 161 Printf.printf "Recent emails:\n\n"; 162 List.iteri (fun i email_json -> 163 match email_json with 164 | `O email_fields -> 165 let subject = match List.assoc_opt "subject" email_fields with 166 | Some (`String s) -> s 167 | _ -> "(no subject)" 168 in 169 let from = match List.assoc_opt "from" email_fields with 170 | Some (`A []) -> "(unknown sender)" 171 | Some (`A ((`O addr_fields)::_)) -> 172 (match List.assoc_opt "email" addr_fields with 173 | Some (`String e) -> 174 (match List.assoc_opt "name" addr_fields with 175 | Some (`String n) -> Printf.sprintf "%s <%s>" n e 176 | _ -> e) 177 | _ -> "(unknown)") 178 | _ -> "(unknown sender)" 179 in 180 let date = match List.assoc_opt "receivedAt" email_fields with 181 | Some (`String d) -> d 182 | _ -> "(unknown date)" 183 in 184 Printf.printf "%d. %s\n" (i + 1) subject; 185 Printf.printf " From: %s\n" from; 186 Printf.printf " Date: %s\n\n" date 187 | _ -> () 188 ) emails 189 | _ -> Printf.printf "No emails in response\n") 190 | _ -> Printf.printf "Unexpected response format\n") 191 | _ -> Printf.printf "Unexpected method response structure\n"); 192 193 Printf.printf "\n✓ Test completed successfully!\n%!" 194 ) else ( 195 Printf.printf "No emails found\n"; 196 Printf.printf "\n✓ Test completed successfully!\n%!" 197 ) 198 with 199 | Failure msg when String.starts_with ~prefix:"JMAP API call failed: HTTP" msg -> 200 Printf.eprintf "API call failed with error: %s\n" msg; 201 Printf.eprintf "This likely means the request JSON is malformed.\n"; 202 exit 1 203 | e -> 204 Printf.eprintf "Error making API call: %s\n%!" (Printexc.to_string e); 205 Printexc.print_backtrace stderr; 206 exit 1)