this repo has no description
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