this repo has no description
at old1 11 kB view raw
1{0 JMAP OCaml Client} 2 3This library provides a type-safe OCaml interface to the JMAP protocol (RFC8620) and JMAP Mail extension (RFC8621). 4 5{1 Overview} 6 7JMAP (JSON Meta Application Protocol) is a modern protocol for synchronizing email, calendars, and contacts designed as a replacement for legacy protocols like IMAP. This OCaml implementation provides: 8 9- Type-safe OCaml interfaces to the JMAP Core and Mail specifications 10- Authentication with username/password or API tokens (Fastmail support) 11- Convenient functions for common email and mailbox operations 12- Support for composing complex multi-part requests with result references 13- Typed handling of message flags, keywords, and mailbox attributes 14 15{1 Getting Started} 16 17{2 Core Modules} 18 19The library is organized into two main packages: 20 21- {!module:Jmap} - Core protocol functionality (RFC8620) 22- {!module:Jmap_mail} - Mail-specific extensions (RFC8621) 23 24{2 Authentication} 25 26To begin working with JMAP, you first need to establish a session: 27 28{[ 29(* Using username/password *) 30let result = Jmap_mail.login 31 ~uri:"https://jmap.example.com/jmap/session" 32 ~credentials:{ 33 username = "user@example.com"; 34 password = "password"; 35 } 36 37(* Using a Fastmail API token *) 38let token = Sys.getenv "JMAP_API_TOKEN" in 39let result = Jmap_mail.login_with_token 40 ~uri:"https://api.fastmail.com/jmap/session" 41 ~api_token:token 42 () 43 44(* Handle the result *) 45match result with 46| Ok conn -> 47 (* Get the primary account ID *) 48 let account_id = 49 let mail_capability = Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail in 50 match List.assoc_opt mail_capability conn.session.primary_accounts with 51 | Some id -> id 52 | None -> (* Use first account or handle error *) 53 in 54 (* Use connection and account_id for further operations *) 55| Error e -> (* Handle error *) 56]} 57 58{2 Working with Mailboxes} 59 60Once authenticated, you can retrieve and manipulate mailboxes: 61 62{[ 63(* Get all mailboxes *) 64let get_mailboxes conn account_id = 65 Jmap_mail.get_mailboxes conn ~account_id 66 67(* Find inbox by role *) 68let find_inbox mailboxes = 69 List.find_opt 70 (fun m -> m.Jmap_mail.Types.role = Some Jmap_mail.Types.Inbox) 71 mailboxes 72]} 73 74{2 Working with Emails} 75 76Retrieve and filter emails: 77 78{[ 79(* Get emails from a mailbox *) 80let get_emails conn account_id mailbox_id = 81 Jmap_mail.get_messages_in_mailbox 82 conn 83 ~account_id 84 ~mailbox_id 85 ~limit:100 86 () 87 88(* Get only unread emails *) 89let is_unread email = 90 List.exists (fun (kw, active) -> 91 (kw = Jmap_mail.Types.Unread || 92 kw = Jmap_mail.Types.Custom "$unread") && active 93 ) email.Jmap_mail.Types.keywords 94 95let get_unread_emails conn account_id mailbox_id = 96 let* result = get_emails conn account_id mailbox_id in 97 match result with 98 | Ok emails -> Lwt.return_ok (List.filter is_unread emails) 99 | Error e -> Lwt.return_error e 100 101(* Filter by sender email *) 102let filter_by_sender emails sender_pattern = 103 List.filter (fun email -> 104 Jmap_mail.email_matches_sender email sender_pattern 105 ) emails 106]} 107 108{2 Message Flags and Keywords} 109 110Work with email flags and keywords: 111 112{[ 113(* Check if an email has a specific keyword *) 114let has_keyword keyword email = 115 List.exists (fun (kw, active) -> 116 match kw, active with 117 | Jmap_mail.Types.Custom k, true when k = keyword -> true 118 | _ -> false 119 ) email.Jmap_mail.Types.keywords 120 121(* Add a keyword to an email *) 122let add_keyword conn account_id email_id keyword = 123 (* This would typically involve creating an Email/set request 124 that updates the keywords property of the email *) 125 failwith "Not fully implemented in this example" 126 127(* Get flag color *) 128let get_flag_color email = 129 Jmap_mail.Types.get_flag_color email.Jmap_mail.Types.keywords 130 131(* Set flag color *) 132let set_flag_color conn account_id email_id color = 133 Jmap_mail.Types.set_flag_color conn account_id email_id color 134]} 135 136{2 Composing Requests with Result References} 137 138JMAP allows composing multiple operations into a single request: 139 140{[ 141(* Example demonstrating result references for chained requests *) 142let demo_result_references conn account_id = 143 let open Jmap.Types in 144 145 (* Create method call IDs *) 146 let mailbox_get_id = "mailboxGet" in 147 let email_query_id = "emailQuery" in 148 let email_get_id = "emailGet" in 149 150 (* First call: Get mailboxes *) 151 let mailbox_get_call = { 152 name = "Mailbox/get"; 153 arguments = `O [ 154 ("accountId", `String account_id); 155 ]; 156 method_call_id = mailbox_get_id; 157 } in 158 159 (* Second call: Query emails in the first mailbox using result reference *) 160 let mailbox_id_ref = Jmap.ResultReference.create 161 ~result_of:mailbox_get_id 162 ~name:"Mailbox/get" 163 ~path:"/list/0/id" in 164 165 let (mailbox_id_ref_key, mailbox_id_ref_value) = 166 Jmap.ResultReference.reference_arg "inMailbox" mailbox_id_ref in 167 168 let email_query_call = { 169 name = "Email/query"; 170 arguments = `O [ 171 ("accountId", `String account_id); 172 ("filter", `O [ 173 (mailbox_id_ref_key, mailbox_id_ref_value) 174 ]); 175 ("limit", `Float 10.0); 176 ]; 177 method_call_id = email_query_id; 178 } in 179 180 (* Third call: Get full email objects using the query result *) 181 let email_ids_ref = Jmap.ResultReference.create 182 ~result_of:email_query_id 183 ~name:"Email/query" 184 ~path:"/ids" in 185 186 let (email_ids_ref_key, email_ids_ref_value) = 187 Jmap.ResultReference.reference_arg "ids" email_ids_ref in 188 189 let email_get_call = { 190 name = "Email/get"; 191 arguments = `O [ 192 ("accountId", `String account_id); 193 (email_ids_ref_key, email_ids_ref_value) 194 ]; 195 method_call_id = email_get_id; 196 } in 197 198 (* Create the complete request with all three method calls *) 199 let request = { 200 using = [ 201 Jmap.Capability.to_string Jmap.Capability.Core; 202 Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail 203 ]; 204 method_calls = [ 205 mailbox_get_call; 206 email_query_call; 207 email_get_call 208 ]; 209 created_ids = None; 210 } in 211 212 (* Execute the request *) 213 Jmap.Api.make_request conn.config request 214]} 215 216{1 Example: List Recent Emails} 217 218Here's a complete example showing how to list recent emails from a mailbox: 219 220{[ 221open Lwt.Syntax 222open Jmap 223open Jmap_mail 224 225(* Main function that demonstrates JMAP functionality *) 226let main () = 227 (* Initialize logging *) 228 Jmap.init_logging ~level:2 ~enable_logs:true ~redact_sensitive:true (); 229 230 (* Check for API token *) 231 match Sys.getenv_opt "JMAP_API_TOKEN" with 232 | None -> 233 Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n"; 234 Lwt.return 1 235 | Some token -> 236 (* Authentication example *) 237 let* login_result = Jmap_mail.login_with_token 238 ~uri:"https://api.fastmail.com/jmap/session" 239 ~api_token:token 240 in 241 242 match login_result with 243 | Error err -> 244 Printf.eprintf "Authentication failed\n"; 245 Lwt.return 1 246 247 | Ok conn -> 248 (* Get primary account ID *) 249 let mail_capability = Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail in 250 let account_id = 251 match List.assoc_opt mail_capability conn.session.primary_accounts with 252 | Some id -> id 253 | None -> 254 match conn.session.accounts with 255 | (id, _) :: _ -> id 256 | [] -> 257 Printf.eprintf "No accounts found\n"; 258 exit 1 259 in 260 261 (* Get mailboxes example *) 262 let* mailboxes_result = Jmap_mail.get_mailboxes conn ~account_id in 263 264 match mailboxes_result with 265 | Error err -> 266 Printf.eprintf "Failed to get mailboxes\n"; 267 Lwt.return 1 268 269 | Ok mailboxes -> 270 (* Use the first mailbox for simplicity *) 271 match mailboxes with 272 | [] -> 273 Printf.eprintf "No mailboxes found\n"; 274 Lwt.return 1 275 276 | first_mailbox :: _ -> 277 (* Get emails example *) 278 let* emails_result = Jmap_mail.get_messages_in_mailbox 279 conn 280 ~account_id 281 ~mailbox_id:first_mailbox.Types.id 282 ~limit:5 283 () 284 in 285 286 match emails_result with 287 | Error err -> 288 Printf.eprintf "Failed to get emails\n"; 289 Lwt.return 1 290 291 | Ok emails -> 292 (* Display emails *) 293 List.iter (fun email -> 294 let module Mail = Jmap_mail.Types in 295 296 (* Get sender *) 297 let sender = match email.Mail.from with 298 | None -> "<unknown>" 299 | Some addrs -> 300 match addrs with 301 | [] -> "<unknown>" 302 | addr :: _ -> 303 match addr.Mail.name with 304 | None -> addr.Mail.email 305 | Some name -> 306 Printf.sprintf "%s <%s>" name addr.Mail.email 307 in 308 309 (* Get subject *) 310 let subject = match email.Mail.subject with 311 | None -> "<no subject>" 312 | Some s -> s 313 in 314 315 (* Is unread? *) 316 let is_unread = List.exists (fun (kw, active) -> 317 match kw with 318 | Mail.Unread -> active 319 | Mail.Custom s when s = "$unread" -> active 320 | _ -> false 321 ) email.Mail.keywords in 322 323 (* Print email info *) 324 Printf.printf "[%s] %s - %s\n" 325 (if is_unread then "UNREAD" else "READ") 326 sender 327 subject 328 ) emails; 329 330 Lwt.return 0 331 332(* Program entry point *) 333let () = 334 let exit_code = Lwt_main.run (main ()) in 335 exit exit_code 336]} 337 338{1 API Reference} 339 340{2 Core Modules} 341 342- {!module:Jmap} - Core JMAP protocol 343 - {!module:Jmap.Types} - Core type definitions 344 - {!module:Jmap.Api} - HTTP client and session handling 345 - {!module:Jmap.ResultReference} - Request composition utilities 346 - {!module:Jmap.Capability} - JMAP capability handling 347 348{2 Mail Extension Modules} 349 350- {!module:Jmap_mail} - JMAP Mail extension 351 - {!module:Jmap_mail.Types} - Mail-specific types 352 - Jmap_mail.Capability - Mail capability handling 353 - Jmap_mail.Json - JSON serialization 354 - Specialized operations for emails, mailboxes, threads, and identities 355 356{1 References} 357 358- {{:https://datatracker.ietf.org/doc/html/rfc8620}} RFC8620: The JSON Meta Application Protocol (JMAP) 359- {{:https://datatracker.ietf.org/doc/html/rfc8621}} RFC8621: The JSON Meta Application Protocol (JMAP) for Mail 360- {{:https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-messageflag-mailboxattribute-02}} Message Flag and Mailbox Attribute Extension