My agentic slop goes here. Not intended for anyone else!
1# JMAP Interface Usage Examples
2
3This document demonstrates how to construct and use all JMAP message types using **only the module interfaces**, without any manual JSON parsing or construction.
4
5All examples use the accessor functions and constructors (`v`) provided by the module signatures.
6
7## Table of Contents
8
91. [Core Protocol Examples](#core-protocol-examples)
102. [Mail Protocol Examples](#mail-protocol-examples)
113. [Client Usage Examples](#client-usage-examples)
124. [Complete Workflow Examples](#complete-workflow-examples)
13
14---
15
16## Core Protocol Examples
17
18### Creating a JMAP Request
19
20```ocaml
21open Jmap_core
22
23(* Create capability URNs *)
24let core_cap = Jmap_capability.core
25let mail_cap = Jmap_capability.mail
26
27(* Create a request with method calls *)
28let request = Jmap_request.make
29 ~using:[core_cap; mail_cap]
30 [
31 (* Method calls go here - see below *)
32 ]
33
34(* Accessing request fields *)
35let caps = Jmap_request.using request
36let calls = Jmap_request.method_calls request
37let created = Jmap_request.created_ids request
38```
39
40### Working with Primitives
41
42```ocaml
43open Jmap_primitives
44
45(* Create IDs *)
46let account_id = Jmap_id.of_string "account123"
47let mailbox_id = Jmap_id.of_string "mailbox456"
48
49(* Create integers *)
50let limit = UnsignedInt.of_int 50
51let offset = Int53.of_int 0
52
53(* Create dates *)
54let now = UTCDate.now ()
55let sent_date = Date.of_string "2024-01-15T10:30:00Z"
56
57(* Access values *)
58let limit_int = UnsignedInt.to_int limit
59let date_str = UTCDate.to_string now
60```
61
62### Building Filters
63
64```ocaml
65open Jmap_filter
66
67(* Simple condition filter *)
68let simple_filter = condition { field = "value" }
69
70(* Complex AND filter *)
71let and_filter = and_ [
72 condition { has_keyword = Some "$flagged" };
73 condition { after = Some now };
74]
75
76(* Complex nested filter *)
77let complex_filter = and_ [
78 or_ [
79 condition { from = Some "alice@example.com" };
80 condition { from = Some "bob@example.com" };
81 ];
82 not_ (condition { has_keyword = Some "$seen" })
83]
84```
85
86### Building Sort Comparators
87
88```ocaml
89open Jmap_comparator
90
91(* Simple ascending sort *)
92let sort_by_date = v ~property:"receivedAt" ()
93
94(* Descending sort with collation *)
95let sort_by_name = v
96 ~property:"name"
97 ~is_ascending:false
98 ~collation:"i;unicode-casemap"
99 ()
100```
101
102### Standard Method Requests
103
104#### Get Request
105
106```ocaml
107open Jmap_standard_methods.Get
108
109(* Get specific objects *)
110let get_req = v
111 ~account_id
112 ~ids:[mailbox_id; Jmap_id.of_string "mailbox789"]
113 ~properties:["id"; "name"; "totalEmails"]
114 ()
115
116(* Get all objects with all properties *)
117let get_all_req = v ~account_id ()
118
119(* Access request fields *)
120let acc_id = account_id get_req
121let ids_opt = ids get_req
122let props_opt = properties get_req
123```
124
125#### Query Request
126
127```ocaml
128open Jmap_standard_methods.Query
129
130(* Complex query *)
131let query_req = v
132 ~account_id
133 ~filter:(Some complex_filter)
134 ~sort:(Some [sort_by_date; sort_by_name])
135 ~position:(Some (Int53.of_int 0))
136 ~limit:(Some (UnsignedInt.of_int 50))
137 ~calculate_total:(Some true)
138 ()
139```
140
141#### Set Request
142
143```ocaml
144open Jmap_standard_methods.Set
145
146(* Create new objects *)
147let set_create_req = v
148 ~account_id
149 ~create:(Some [
150 (Jmap_id.of_string "temp1", new_obj1);
151 (Jmap_id.of_string "temp2", new_obj2);
152 ])
153 ()
154
155(* Update objects using PatchObject *)
156let set_update_req = v
157 ~account_id
158 ~update:(Some [
159 (existing_id, [
160 ("name", Some (`String "New Name"));
161 ("archived", Some (`Bool true));
162 ]);
163 ])
164 ()
165
166(* Destroy objects *)
167let set_destroy_req = v
168 ~account_id
169 ~destroy:(Some [id1; id2; id3])
170 ()
171
172(* Combined create/update/destroy with state check *)
173let set_combined_req = v
174 ~account_id
175 ~if_in_state:(Some "expectedState123")
176 ~create:(Some [...])
177 ~update:(Some [...])
178 ~destroy:(Some [...])
179 ()
180```
181
182---
183
184## Mail Protocol Examples
185
186### Creating a Mailbox
187
188```ocaml
189open Jmap_mail.Jmap_mailbox
190
191(* Create mailbox rights *)
192let full_rights = Rights.v
193 ~may_read_items:true
194 ~may_add_items:true
195 ~may_remove_items:true
196 ~may_set_seen:true
197 ~may_set_keywords:true
198 ~may_create_child:true
199 ~may_rename:true
200 ~may_delete:true
201 ~may_submit:true
202
203(* Create a mailbox *)
204let inbox = v
205 ~id:(Jmap_id.of_string "inbox123")
206 ~name:"Inbox"
207 ~role:(Some "inbox")
208 ~sort_order:(UnsignedInt.of_int 10)
209 ~total_emails:(UnsignedInt.of_int 1542)
210 ~unread_emails:(UnsignedInt.of_int 127)
211 ~total_threads:(UnsignedInt.of_int 890)
212 ~unread_threads:(UnsignedInt.of_int 95)
213 ~my_rights:full_rights
214 ~is_subscribed:true
215 ()
216
217(* Create a sub-mailbox *)
218let archive = v
219 ~id:(Jmap_id.of_string "archive456")
220 ~name:"Archive"
221 ~parent_id:(Some (Jmap_id.of_string "inbox123"))
222 ~role:(Some "archive")
223 ~sort_order:(UnsignedInt.of_int 20)
224 ~total_emails:(UnsignedInt.of_int 5231)
225 ~unread_emails:(UnsignedInt.of_int 0)
226 ~total_threads:(UnsignedInt.of_int 3452)
227 ~unread_threads:(UnsignedInt.of_int 0)
228 ~my_rights:full_rights
229 ~is_subscribed:false
230 ()
231
232(* Access mailbox fields *)
233let mailbox_name = name inbox
234let parent = parent_id archive
235let unread_count = UnsignedInt.to_int (unread_emails inbox)
236let can_delete = Rights.may_delete (my_rights inbox)
237```
238
239### Mailbox Query with Filters
240
241```ocaml
242open Jmap_mail.Jmap_mailbox
243
244(* Create mailbox filter *)
245let filter = Filter.v
246 ~parent_id:(Some inbox_id)
247 ~has_any_role:(Some false)
248 ~is_subscribed:(Some true)
249 ()
250
251(* Query with tree sorting *)
252let query_req = Query.v
253 ~account_id
254 ~filter:(Some (Jmap_filter.condition filter))
255 ~sort_as_tree:(Some true)
256 ~filter_as_tree:(Some true)
257 ()
258```
259
260### Creating an Email
261
262```ocaml
263open Jmap_mail.Jmap_email
264
265(* Create email addresses *)
266let from_addr = EmailAddress.v
267 ~name:(Some "Alice Smith")
268 ~email:"alice@example.com"
269
270let to_addr = EmailAddress.v
271 ~email:"bob@example.com" (* name is optional *)
272
273(* Create a simple text email *)
274let text_body = BodyPart.v
275 ~part_id:(Some "1")
276 ~blob_id:(Some blob_id)
277 ~size:(UnsignedInt.of_int 1234)
278 ~headers:[]
279 ~type_:"text/plain"
280 ~charset:(Some "utf-8")
281 ()
282
283(* Create email with all common fields *)
284let email = v
285 ~id:(Jmap_id.of_string "email123")
286 ~blob_id:(Jmap_id.of_string "blob456")
287 ~thread_id:(Jmap_id.of_string "thread789")
288 ~mailbox_ids:[(inbox_id, true); (drafts_id, true)]
289 ~keywords:[("$seen", true); ("$flagged", true)]
290 ~size:(UnsignedInt.of_int 15234)
291 ~received_at:(UTCDate.now ())
292 ~from:(Some [from_addr])
293 ~to_:(Some [to_addr])
294 ~subject:(Some "Important Meeting")
295 ~sent_at:(Some sent_date)
296 ~text_body:(Some [text_body])
297 ~has_attachment:false
298 ~preview:"This is a preview of the email..."
299 ()
300
301(* Access email fields *)
302let subject = subject email
303let sender = from email
304let is_flagged = List.mem ("$flagged", true) (keywords email)
305```
306
307### Email with Attachments
308
309```ocaml
310open Jmap_mail.Jmap_email
311
312(* Create multipart structure *)
313let text_part = BodyPart.v
314 ~part_id:(Some "1")
315 ~size:(UnsignedInt.of_int 500)
316 ~headers:[]
317 ~type_:"text/plain"
318 ~charset:(Some "utf-8")
319 ()
320
321let attachment = BodyPart.v
322 ~part_id:(Some "2")
323 ~blob_id:(Some attachment_blob_id)
324 ~size:(UnsignedInt.of_int 45000)
325 ~headers:[]
326 ~name:(Some "document.pdf")
327 ~type_:"application/pdf"
328 ~disposition:(Some "attachment")
329 ()
330
331let multipart = BodyPart.v
332 ~size:(UnsignedInt.of_int 45500)
333 ~headers:[]
334 ~type_:"multipart/mixed"
335 ~sub_parts:(Some [text_part; attachment])
336 ()
337
338let email_with_attachment = v
339 ~id
340 ~blob_id
341 ~thread_id
342 ~mailbox_ids:[(inbox_id, true)]
343 ~size:(UnsignedInt.of_int 45500)
344 ~received_at:(UTCDate.now ())
345 ~body_structure:(Some multipart)
346 ~attachments:(Some [attachment])
347 ~has_attachment:true
348 ~preview:"Email with PDF attachment"
349 ()
350```
351
352### Email Query with Complex Filters
353
354```ocaml
355open Jmap_mail.Jmap_email
356
357(* Create email filter *)
358let email_filter = Filter.v
359 ~in_mailbox:(Some inbox_id)
360 ~after:(Some (UTCDate.of_string "2024-01-01T00:00:00Z"))
361 ~has_keyword:(Some "$flagged")
362 ~not_keyword:(Some "$seen")
363 ~from:(Some "important@example.com")
364 ~has_attachment:(Some true)
365 ()
366
367(* Create query with filter *)
368let email_query = Query.v
369 ~account_id
370 ~filter:(Some (Jmap_filter.condition email_filter))
371 ~sort:(Some [
372 Jmap_comparator.v ~property:"receivedAt" ~is_ascending:false ()
373 ])
374 ~collapse_threads:(Some true)
375 ~limit:(Some (UnsignedInt.of_int 25))
376 ()
377```
378
379### Email Import
380
381```ocaml
382open Jmap_mail.Jmap_email.Import
383
384(* Create import email object *)
385let import_email = import_email_v
386 ~blob_id:raw_message_blob_id
387 ~mailbox_ids:[(inbox_id, true)]
388 ~keywords:[("$seen", true)]
389 ()
390
391(* Create import request *)
392let import_req = request_v
393 ~account_id
394 ~emails:[
395 (Jmap_id.of_string "import1", import_email);
396 ]
397 ()
398```
399
400### Creating an Identity
401
402```ocaml
403open Jmap_mail.Jmap_identity
404
405let signature_html = "<p>Best regards,<br>Alice Smith<br><i>CEO</i></p>"
406
407let identity = v
408 ~id:(Jmap_id.of_string "identity123")
409 ~name:"Work Identity"
410 ~email:"alice@company.com"
411 ~reply_to:(Some [EmailAddress.v ~email:"noreply@company.com"])
412 ~bcc:(Some [EmailAddress.v ~email:"archive@company.com"])
413 ~text_signature:"Best regards,\nAlice Smith\nCEO"
414 ~html_signature:signature_html
415 ~may_delete:true
416 ()
417```
418
419### Email Submission
420
421```ocaml
422open Jmap_mail.Jmap_email_submission
423
424(* Create SMTP envelope *)
425let envelope = Envelope.v
426 ~mail_from:(Address.v ~email:"alice@example.com" ())
427 ~rcpt_to:[
428 Address.v ~email:"bob@example.com" ();
429 Address.v ~email:"carol@example.com" ();
430 ]
431
432(* Create email submission *)
433let submission = v
434 ~id:(Jmap_id.of_string "submission123")
435 ~identity_id
436 ~email_id
437 ~thread_id
438 ~envelope:(Some envelope)
439 ~send_at:(UTCDate.now ())
440 ~undo_status:"pending"
441 ()
442
443(* Access submission state *)
444let can_undo = (undo_status submission) = "pending"
445let send_time = send_at submission
446```
447
448### Vacation Response
449
450```ocaml
451open Jmap_mail.Jmap_vacation_response
452
453let vacation = v
454 ~id:(Jmap_id.of_string "singleton")
455 ~is_enabled:true
456 ~from_date:(Some (UTCDate.of_string "2024-12-20T00:00:00Z"))
457 ~to_date:(Some (UTCDate.of_string "2024-12-31T23:59:59Z"))
458 ~subject:(Some "Out of Office")
459 ~text_body:(Some "I'm away until January. Will respond when I return.")
460 ~html_body:(Some "<p>I'm away until January. Will respond when I return.</p>")
461 ()
462
463(* Check if active *)
464let is_active = is_enabled vacation
465```
466
467---
468
469## Client Usage Examples
470
471### Creating a Client Connection
472
473```ocaml
474open Jmap_client
475open Jmap_connection
476
477(* Create custom config *)
478let config = config_v
479 ~max_retries:5
480 ~timeout:60.0
481 ~user_agent:"MyApp/1.0"
482 ()
483
484(* Create connection with basic auth *)
485let conn = v
486 ~config
487 ~auth:(basic "user@example.com" "password123")
488 ()
489
490(* Create connection with bearer token *)
491let oauth_conn = v
492 ~config
493 ~auth:(bearer "oauth_token_here")
494 ()
495
496(* Access connection properties *)
497let max_retries = config_max_retries (config conn)
498let auth_method = auth conn
499```
500
501---
502
503## Complete Workflow Examples
504
505### Example 1: Fetch and Display Mailboxes
506
507```ocaml
508open Lwt.Syntax
509open Jmap_core
510open Jmap_mail.Jmap_mailbox
511
512let display_mailboxes client account_id =
513 (* Create get request *)
514 let get_req = Get.v ~account_id ()
515
516 (* Create JMAP request *)
517 let request = Jmap_request.make
518 ~using:[Jmap_capability.core; Jmap_capability.mail]
519 [Jmap_invocation.Packed {
520 (* Would need to properly construct invocation *)
521 }]
522 in
523
524 (* Execute request *)
525 let* response = Jmap_client.call client request in
526
527 (* Process response *)
528 let method_responses = Jmap_response.method_responses response in
529 (* Extract mailboxes and display *)
530
531 Lwt.return_unit
532```
533
534### Example 2: Search and Display Emails
535
536```ocaml
537open Jmap_mail.Jmap_email
538
539let search_flagged_emails client account_id inbox_id =
540 (* Build filter *)
541 let filter = Filter.v
542 ~in_mailbox:(Some inbox_id)
543 ~has_keyword:(Some "$flagged")
544 ~not_keyword:(Some "$seen")
545 ()
546
547 (* Build query *)
548 let query = Query.v
549 ~account_id
550 ~filter:(Some (Jmap_filter.condition filter))
551 ~sort:(Some [
552 Jmap_comparator.v ~property:"receivedAt" ~is_ascending:false ()
553 ])
554 ~limit:(Some (UnsignedInt.of_int 20))
555 ()
556
557 (* Execute query and fetch results *)
558 (* ... *)
559```
560
561### Example 3: Create and Send Email
562
563```ocaml
564open Jmap_mail
565
566let send_email client account_id identity_id =
567 (* Create email addresses *)
568 let from = Jmap_email.EmailAddress.v
569 ~name:(Some "Alice")
570 ~email:"alice@example.com" in
571 let to_ = Jmap_email.EmailAddress.v
572 ~email:"bob@example.com" in
573
574 (* Create email body *)
575 let body = Jmap_email.BodyPart.v
576 ~part_id:(Some "1")
577 ~size:(UnsignedInt.of_int 100)
578 ~headers:[]
579 ~type_:"text/plain"
580 ~charset:(Some "utf-8")
581 ()
582
583 (* Create draft email *)
584 let email = Jmap_email.v
585 ~id:(Jmap_id.of_string "draft1")
586 ~blob_id:(Jmap_id.of_string "blob1")
587 ~thread_id:(Jmap_id.of_string "thread1")
588 ~mailbox_ids:[(Jmap_id.of_string "drafts", true)]
589 ~keywords:[("$draft", true)]
590 ~size:(UnsignedInt.of_int 100)
591 ~received_at:(UTCDate.now ())
592 ~from:(Some [from])
593 ~to_:(Some [to_])
594 ~subject:(Some "Hello")
595 ~text_body:(Some [body])
596 ~has_attachment:false
597 ~preview:"Hello, how are you?"
598 ()
599
600 (* Create submission *)
601 let submission = Jmap_email_submission.v
602 ~id:(Jmap_id.of_string "sub1")
603 ~identity_id
604 ~email_id:(Jmap_email.id email)
605 ~thread_id:(Jmap_email.thread_id email)
606 ~send_at:(UTCDate.now ())
607 ~undo_status:"pending"
608 ()
609
610 (* Execute send *)
611 (* ... *)
612```
613
614### Example 4: Update Mailbox Hierarchy
615
616```ocaml
617open Jmap_mail.Jmap_mailbox
618
619let reorganize_mailboxes client account_id =
620 (* Create new archive folder *)
621 let new_archive = v
622 ~id:(Jmap_id.of_string "temp1")
623 ~name:"Archive 2024"
624 ~parent_id:(Some (Jmap_id.of_string "archive"))
625 ~sort_order:(UnsignedInt.of_int 10)
626 ~total_emails:(UnsignedInt.of_int 0)
627 ~unread_emails:(UnsignedInt.of_int 0)
628 ~total_threads:(UnsignedInt.of_int 0)
629 ~unread_threads:(UnsignedInt.of_int 0)
630 ~my_rights:full_rights
631 ~is_subscribed:true
632 ()
633
634 (* Create set request *)
635 let set_req = Jmap_standard_methods.Set.v
636 ~account_id
637 ~create:(Some [(Jmap_id.of_string "temp1", new_archive)])
638 ()
639
640 (* Execute *)
641 (* ... *)
642```
643
644---
645
646## Key Patterns
647
648### 1. **All Required Fields First**
649```ocaml
650let obj = v
651 ~required1
652 ~required2
653 ~optional1:(Some value)
654 ()
655```
656
657### 2. **Optional Fields Default to None**
658```ocaml
659let obj = v ~id ~name () (* All other fields are None/[] *)
660```
661
662### 3. **Response Field Access**
663```ocaml
664(* For request/response pairs *)
665let req_id = account_id request
666let resp_id = response_account_id response
667```
668
669### 4. **Submodule Pattern**
670```ocaml
671let rights = Rights.v ~may_read_items:true ~may_delete:false (...)
672let mailbox = v ~id ~name ~my_rights:rights ()
673```
674
675### 5. **Filter Composition**
676```ocaml
677let filter = Jmap_filter.and_ [
678 condition { field1 = value1 };
679 or_ [
680 condition { field2 = value2 };
681 condition { field3 = value3 };
682 ]
683]
684```
685
686---
687
688## Summary
689
690This document shows that **all JMAP message types can be constructed using only the module interfaces** with:
691
692- ✅ Type-safe constructors with labeled arguments
693- ✅ Clear separation of required vs optional fields
694- ✅ Accessor functions for all fields
695- ✅ No manual JSON manipulation required
696- ✅ Composable filters and queries
697- ✅ Complete coverage of core and mail protocols
698
699The interface design ensures compile-time safety while remaining flexible and ergonomic for all JMAP use cases.