My agentic slop goes here. Not intended for anyone else!
1open Printf
2
3(* Result monad operator for cleaner error handling *)
4let (let+) x f = Result.bind x f
5
6let fetch_recent_emails env ctx session =
7 try
8 let account_id_str = Jmap_unix.Session_utils.get_primary_mail_account session in
9 let account_id = match Jmap.Id.of_string account_id_str with
10 | Ok id -> id
11 | Error err -> failwith ("Invalid account ID: " ^ err) in
12 printf "Using account: %s\nBuilding JMAP request using type-safe capabilities...\n" account_id_str;
13
14 let query_json =
15 Jmap_email.Query.(query () |> with_account account_id |> order_by Sort.by_date_desc |> limit 5 |> build_email_query) in
16
17 let get_json =
18 Jmap_email.Query.(build_email_get_with_ref ~account_id
19 ~properties:[`Id; `ThreadId; `From; `Subject; `ReceivedAt; `Preview; `Keywords; `HasAttachment]
20 ~result_of:"q1") in
21
22 let builder = Jmap_unix.build ctx in
23 let builder = Jmap_unix.using builder [`Core; `Mail] in
24 let builder = Jmap_unix.add_method_call builder `Email_query query_json "q1" in
25 let builder = Jmap_unix.add_method_call builder `Email_get get_json "g1" in
26
27 let+ response = Jmap_unix.execute env builder in
28 printf "✓ Got JMAP response\n";
29
30 let+ query_response_json = Jmap_unix.Response.extract_method ~method_name:`Email_query ~method_call_id:"q1" response in
31 let+ query_response = Jmap_email.Response.parse_query_response query_response_json in
32 printf "✓ Found %d emails\n\n" (Jmap_email.Response.ids_from_query_response query_response |> List.length);
33
34 let+ get_response_json = Jmap_unix.Response.extract_method ~method_name:`Email_get ~method_call_id:"g1" response in
35 let+ get_response = Jmap_email.Response.parse_get_response
36 ~from_json:(fun json -> match Jmap_email.Email.of_json json with
37 | Ok email -> email
38 | Error err -> failwith ("Email parse error: " ^ err))
39 get_response_json in
40
41 let emails = Jmap_email.Response.emails_from_get_response get_response in
42
43 let print_sender email =
44 Jmap_email.Email.(match from email with
45 | Some (sender :: _) ->
46 Jmap_email.Address.(printf " From: %s\n"
47 (match name sender with | Some n -> n ^ " <" ^ email sender ^ ">" | None -> email sender))
48 | _ -> printf " From: (Unknown)\n") in
49
50 let print_preview email =
51 Jmap_email.Email.(match preview email with
52 | Some p when String.length p > 0 ->
53 let preview = if String.length p > 100 then String.sub p 0 97 ^ "..." else p in
54 printf " Preview: %s\n" preview
55 | _ -> ()) in
56
57 List.iteri (fun i email ->
58 printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nEmail #%d:\n" (i + 1);
59 printf " Subject: %s\n" (Jmap_email.Email.subject email |> Option.value ~default:"(No Subject)");
60 print_sender email;
61 Jmap_email.Email.(received_at email |> Option.iter (fun t ->
62 printf " Date: %s\n" (Jmap.Date.to_rfc3339 t)));
63 print_preview email
64 ) emails;
65 printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
66 Ok ()
67 with
68 | exn -> Error (Jmap.Error.protocol_error ("Exception: " ^ Printexc.to_string exn))
69
70let main () =
71 (* Initialize the random number generator for TLS *)
72 Mirage_crypto_rng_unix.use_default ();
73
74 Eio_main.run @@ fun env ->
75
76 printf "JMAP Fastmail Connection & Foundation Test\n";
77 printf "==========================================\n\n";
78
79 (* Test basic JMAP types first *)
80 printf "Testing core JMAP modules...\n";
81
82 let test_modules = [
83 ("Jmap.Id", Jmap.Id.(of_string "test-id-123" |> Result.map (Format.asprintf "%a" pp)));
84 ("Jmap.Date", Ok (Jmap.Date.(Unix.time () |> of_timestamp |> to_timestamp |> Printf.sprintf "%.0f")));
85 ("Jmap.UInt", Jmap.UInt.(of_int 42 |> Result.map (Format.asprintf "%a" pp)));
86 ] in
87
88 let test_results = List.map (fun (name, result) -> match result with
89 | Ok value -> printf "✓ %s creation: %s\n" name value; true
90 | Error e -> printf "✗ %s creation failed: %s\n" name e; false
91 ) test_modules in
92
93 if not (List.for_all (fun x -> x) test_results) then (
94 printf "\nCore module tests failed - aborting\n";
95 exit 1
96 );
97
98 printf "✓ All core modules working\n\n";
99
100 (* Read API credentials *)
101 let api_key =
102 try
103 let ic = open_in ".api-key" in
104 let key = input_line ic in
105 close_in ic;
106 String.trim key
107 with
108 | Sys_error _ ->
109 eprintf "Info: Create a .api-key file with your JMAP bearer token to test full connectivity\n";
110 eprintf " You can get this from Fastmail Settings > Privacy & Security > API Keys\n\n";
111 printf "Overall: ALL TESTS PASSED\n";
112 printf "\nOffline test complete - all core JMAP functionality working!\n";
113 exit 0
114 in
115
116 try
117 (* Step 1: Connect to JMAP server *)
118 printf "Connecting to Fastmail JMAP server...\n";
119 let client = Jmap_unix.create_client () in
120 let session_url = Uri.of_string "https://api.fastmail.com/jmap/session" in
121 let auth_method = Jmap_unix.Bearer api_key in
122
123 match Jmap_unix.(connect env client ~session_url ~host:"api.fastmail.com" ~port:443 ~use_tls:true ~auth_method ()) with
124 | Ok (ctx, session) ->
125 printf "✓ Connected successfully\n\n";
126 Jmap_unix.Session_utils.print_session_info session;
127
128 printf "\n📧 Fetching recent emails...\n";
129 (match fetch_recent_emails env ctx session with
130 | Ok () -> printf "✓ Email fetch completed successfully\n"
131 | Error error -> Format.printf "⚠ Email fetch failed: %a\n" Jmap.Error.pp error);
132
133 printf "\nClosing connection...\n";
134 (match Jmap_unix.close ctx with
135 | Ok () -> printf "✓ Connection closed successfully\n"
136 | Error error -> Format.printf "⚠ Error closing connection: %a\n" Jmap.Error.pp error);
137
138 printf "\nOverall: ALL TESTS PASSED\n"
139
140 | Error error ->
141 Format.eprintf "✗ Connection failed: %a\n"
142 Jmap.Error.pp error;
143 eprintf "\nThis could be due to:\n";
144 eprintf " - Invalid API key\n";
145 eprintf " - Network connectivity issues\n";
146 eprintf " - Fastmail service unavailability\n\n";
147 exit 1
148
149 with
150 | Failure msg ->
151 printf "Error: %s\n" msg;
152 exit 1
153 | exn ->
154 printf "Unexpected error: %s\n" (Printexc.to_string exn);
155 exit 1
156
157let () = main ()