This repository has no description
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Unified JMAP interface for OCaml
7
8 This module provides a clean, ergonomic API for working with JMAP
9 (RFC 8620/8621), combining the protocol and mail layers with abstract
10 types and polymorphic variants.
11
12 {2 Quick Start}
13
14 {[
15 open Jmap
16
17 (* Keywords use polymorphic variants *)
18 let is_unread email =
19 not (List.mem `Seen (Email.keywords email))
20
21 (* Mailbox roles are also polymorphic *)
22 let find_inbox mailboxes =
23 List.find_opt (fun m -> Mailbox.role m = Some `Inbox) mailboxes
24 ]}
25
26 {2 Module Structure}
27
28 - {!Proto} - Low-level protocol and mail types (RFC 8620/8621)
29 - {!module-Error}, {!Id}, {!Keyword}, {!Role}, {!Capability} - Core types
30 - {!Session}, {!Email}, {!Mailbox}, etc. - Abstract type accessors
31*)
32
33(** {1 Protocol Layer Re-exports} *)
34
35(** Low-level JMAP protocol types (RFC 8620/8621).
36
37 These are the raw protocol and mail types. For most use cases, prefer the
38 higher-level types in this module. *)
39module Proto = Jmap_proto
40
41(** {1 Core Types} *)
42
43(** Unified error type for JMAP operations. *)
44module Error : sig
45 (** Request-level error (RFC 7807 Problem Details). *)
46 type request = {
47 type_ : string;
48 status : int option;
49 title : string option;
50 detail : string option;
51 limit : string option;
52 }
53
54 (** Method-level error. *)
55 type method_ = {
56 type_ : string;
57 description : string option;
58 }
59
60 (** Set operation error for a specific object. *)
61 type set = {
62 type_ : string;
63 description : string option;
64 properties : string list option;
65 }
66
67 (** Unified error type.
68
69 All errors from JSON parsing, HTTP, session management, and JMAP method
70 calls are represented as polymorphic variants. *)
71 type t = [
72 | `Request of request
73 | `Method of method_
74 | `Set of string * set
75 | `Json of string
76 | `Http of int * string
77 | `Connection of string
78 | `Session of string
79 ]
80
81 val pp : Format.formatter -> t -> unit
82 val to_string : t -> string
83end
84
85(** JMAP identifier type. *)
86module Id : sig
87 type t
88
89 val of_string : string -> (t, string) result
90 val of_string_exn : string -> t
91 val to_string : t -> string
92 val compare : t -> t -> int
93 val equal : t -> t -> bool
94 val pp : Format.formatter -> t -> unit
95end
96
97(** Email keyword type.
98
99 Standard keywords are represented as polymorphic variants.
100 Custom keywords use [`Custom of string]. *)
101module Keyword : sig
102 (** RFC 8621 standard keywords *)
103 type standard = [
104 | `Seen
105 | `Flagged
106 | `Answered
107 | `Draft
108 | `Forwarded
109 | `Phishing
110 | `Junk
111 | `NotJunk
112 ]
113
114 (** draft-ietf-mailmaint extended keywords *)
115 type extended = [
116 | `Notify
117 | `Muted
118 | `Followed
119 | `Memo
120 | `HasMemo
121 | `HasAttachment
122 | `HasNoAttachment
123 | `AutoSent
124 | `Unsubscribed
125 | `CanUnsubscribe
126 | `Imported
127 | `IsTrusted
128 | `MaskedEmail
129 | `New
130 ]
131
132 (** Apple Mail flag color keywords *)
133 type flag_bits = [
134 | `MailFlagBit0
135 | `MailFlagBit1
136 | `MailFlagBit2
137 ]
138
139 type t = [
140 | standard
141 | extended
142 | flag_bits
143 | `Custom of string
144 ]
145
146 val of_string : string -> t
147 val to_string : t -> string
148 val pp : Format.formatter -> t -> unit
149
150 (** Apple Mail flag colors *)
151 type flag_color = [
152 | `Red
153 | `Orange
154 | `Yellow
155 | `Green
156 | `Blue
157 | `Purple
158 | `Gray
159 ]
160
161 val flag_color_of_keywords : t list -> flag_color option
162 (** [flag_color_of_keywords keywords] extracts the flag color from a list
163 of keywords. Returns [None] for invalid bit combinations. *)
164
165 val flag_color_to_keywords : flag_color -> t list
166 (** [flag_color_to_keywords color] returns the keywords to set for the color. *)
167end
168
169(** Mailbox role type.
170
171 Standard roles are represented as polymorphic variants.
172 Custom roles use [`Custom of string]. *)
173module Role : sig
174 (** RFC 8621 standard roles *)
175 type standard = [
176 | `Inbox
177 | `Sent
178 | `Drafts
179 | `Trash
180 | `Junk
181 | `Archive
182 | `Flagged
183 | `Important
184 | `All
185 | `Subscribed
186 ]
187
188 (** draft-ietf-mailmaint extended roles *)
189 type extended = [
190 | `Snoozed
191 | `Scheduled
192 | `Memos
193 ]
194
195 type t = [
196 | standard
197 | extended
198 | `Custom of string
199 ]
200
201 val of_string : string -> t
202 val to_string : t -> string
203 val pp : Format.formatter -> t -> unit
204end
205
206(** JMAP capability type.
207
208 Standard capabilities are represented as polymorphic variants.
209 Custom capabilities use [`Custom of string]. *)
210module Capability : sig
211 type t = [
212 | `Core
213 | `Mail
214 | `Submission
215 | `VacationResponse
216 | `Custom of string
217 ]
218
219 val core_uri : string
220 val mail_uri : string
221 val submission_uri : string
222 val vacation_uri : string
223
224 val of_string : string -> t
225 val to_string : t -> string
226 val pp : Format.formatter -> t -> unit
227end
228
229(** {1 Session Types} *)
230
231(** JMAP session information. *)
232module Session : sig
233 (** Account information. *)
234 module Account : sig
235 type t
236
237 val name : t -> string
238 val is_personal : t -> bool
239 val is_read_only : t -> bool
240 end
241
242 type t
243
244 val capabilities : t -> (string * Jsont.json) list
245 val accounts : t -> (Id.t * Account.t) list
246 val primary_accounts : t -> (string * Id.t) list
247 val username : t -> string
248 val api_url : t -> string
249 val download_url : t -> string
250 val upload_url : t -> string
251 val event_source_url : t -> string
252 val state : t -> string
253
254 val get_account : Id.t -> t -> Account.t option
255 val primary_account_for : string -> t -> Id.t option
256 val has_capability : string -> t -> bool
257end
258
259(** {1 Mail Types} *)
260
261(** Email address with optional display name. *)
262module Email_address : sig
263 type t
264
265 val name : t -> string option
266 val email : t -> string
267 val create : ?name:string -> string -> t
268end
269
270(** Email mailbox.
271 All accessors return option types since responses only include requested properties. *)
272module Mailbox : sig
273 type t
274
275 val id : t -> Id.t option
276 val name : t -> string option
277 val parent_id : t -> Id.t option
278 val sort_order : t -> int64 option
279 val total_emails : t -> int64 option
280 val unread_emails : t -> int64 option
281 val total_threads : t -> int64 option
282 val unread_threads : t -> int64 option
283 val is_subscribed : t -> bool option
284 val role : t -> Role.t option
285
286 (** Mailbox rights. *)
287 module Rights : sig
288 type t
289
290 val may_read_items : t -> bool
291 val may_add_items : t -> bool
292 val may_remove_items : t -> bool
293 val may_set_seen : t -> bool
294 val may_set_keywords : t -> bool
295 val may_create_child : t -> bool
296 val may_rename : t -> bool
297 val may_delete : t -> bool
298 val may_submit : t -> bool
299 end
300
301 val my_rights : t -> Rights.t option
302end
303
304(** Email thread.
305 All accessors return option types since responses only include requested properties. *)
306module Thread : sig
307 type t
308
309 val id : t -> Id.t option
310 val email_ids : t -> Id.t list option
311end
312
313(** Email message. *)
314module Email : sig
315 (** Email body part. *)
316 module Body : sig
317 type part
318 type value
319
320 val part_id : part -> string option
321 val blob_id : part -> Id.t option
322 val size : part -> int64 option
323 val name : part -> string option
324 val type_ : part -> string
325 val charset : part -> string option
326 val disposition : part -> string option
327 val cid : part -> string option
328 val language : part -> string list option
329 val location : part -> string option
330
331 val value_text : value -> string
332 val value_is_truncated : value -> bool
333 val value_is_encoding_problem : value -> bool
334 end
335
336 (** All accessors return option types since responses only include requested properties. *)
337 type t
338
339 val id : t -> Id.t option
340 val blob_id : t -> Id.t option
341 val thread_id : t -> Id.t option
342 val mailbox_ids : t -> (Id.t * bool) list option
343 val size : t -> int64 option
344 val received_at : t -> Ptime.t option
345 val message_id : t -> string list option
346 val in_reply_to : t -> string list option
347 val references : t -> string list option
348 val subject : t -> string option
349 val sent_at : t -> Ptime.t option
350 val has_attachment : t -> bool option
351 val preview : t -> string option
352
353 (** Get active keywords as polymorphic variants.
354 Returns empty list if keywords property was not requested. *)
355 val keywords : t -> Keyword.t list
356
357 (** Check if email has a specific keyword.
358 Returns false if keywords property was not requested. *)
359 val has_keyword : Keyword.t -> t -> bool
360
361 val from : t -> Email_address.t list option
362 val to_ : t -> Email_address.t list option
363 val cc : t -> Email_address.t list option
364 val bcc : t -> Email_address.t list option
365 val reply_to : t -> Email_address.t list option
366 val sender : t -> Email_address.t list option
367
368 val text_body : t -> Body.part list option
369 val html_body : t -> Body.part list option
370 val attachments : t -> Body.part list option
371 val body_values : t -> (string * Body.value) list option
372end
373
374(** Email identity for sending.
375 All accessors return option types since responses only include requested properties. *)
376module Identity : sig
377 type t
378
379 val id : t -> Id.t option
380 val name : t -> string option
381 val email : t -> string option
382 val reply_to : t -> Email_address.t list option
383 val bcc : t -> Email_address.t list option
384 val text_signature : t -> string option
385 val html_signature : t -> string option
386 val may_delete : t -> bool option
387end
388
389(** Email submission for outgoing mail.
390 All accessors return option types since responses only include requested properties. *)
391module Submission : sig
392 type t
393
394 val id : t -> Id.t option
395 val identity_id : t -> Id.t option
396 val email_id : t -> Id.t option
397 val thread_id : t -> Id.t option
398 val send_at : t -> Ptime.t option
399 val undo_status : t -> Proto.Submission.undo_status option
400 val delivery_status : t -> (string * Proto.Submission.Delivery_status.t) list option
401 val dsn_blob_ids : t -> Id.t list option
402 val mdn_blob_ids : t -> Id.t list option
403end
404
405(** Vacation auto-response. *)
406module Vacation : sig
407 type t
408
409 val id : t -> Id.t
410 val is_enabled : t -> bool
411 val from_date : t -> Ptime.t option
412 val to_date : t -> Ptime.t option
413 val subject : t -> string option
414 val text_body : t -> string option
415 val html_body : t -> string option
416end
417
418(** Search snippet with highlighted matches. *)
419module Search_snippet : sig
420 type t
421
422 val email_id : t -> Id.t
423 val subject : t -> string option
424 val preview : t -> string option
425end
426
427(** {1 Filter Types} *)
428
429(** Email filter conditions for queries. *)
430module Email_filter : sig
431 type condition
432
433 (** Create an email filter condition.
434
435 All parameters are optional. Omitted parameters are not included
436 in the filter. Use [make ()] for an empty filter. *)
437 val make :
438 ?in_mailbox:Id.t ->
439 ?in_mailbox_other_than:Id.t list ->
440 ?before:Ptime.t ->
441 ?after:Ptime.t ->
442 ?min_size:int64 ->
443 ?max_size:int64 ->
444 ?all_in_thread_have_keyword:Keyword.t ->
445 ?some_in_thread_have_keyword:Keyword.t ->
446 ?none_in_thread_have_keyword:Keyword.t ->
447 ?has_keyword:Keyword.t ->
448 ?not_keyword:Keyword.t ->
449 ?has_attachment:bool ->
450 ?text:string ->
451 ?from:string ->
452 ?to_:string ->
453 ?cc:string ->
454 ?bcc:string ->
455 ?subject:string ->
456 ?body:string ->
457 ?header:(string * string option) ->
458 unit -> condition
459end
460
461(** Mailbox filter conditions for queries. *)
462module Mailbox_filter : sig
463 type condition
464
465 (** Create a mailbox filter condition.
466
467 All parameters are optional.
468 For [role]: [Some (Some r)] filters by role [r], [Some None] filters for
469 mailboxes with no role, [None] doesn't filter by role. *)
470 val make :
471 ?parent_id:Id.t option ->
472 ?name:string ->
473 ?role:Role.t option ->
474 ?has_any_role:bool ->
475 ?is_subscribed:bool ->
476 unit -> condition
477end
478
479(** {1 Response Types} *)
480
481(** Generic /get response wrapper. *)
482module Get_response : sig
483 type 'a t
484
485 val account_id : 'a t -> Id.t
486 val state : 'a t -> string
487 val list : 'a t -> 'a list
488 val not_found : 'a t -> Id.t list
489end
490
491(** Query response. *)
492module Query_response : sig
493 type t
494
495 val account_id : t -> Id.t
496 val query_state : t -> string
497 val can_calculate_changes : t -> bool
498 val position : t -> int64
499 val ids : t -> Id.t list
500 val total : t -> int64 option
501end
502
503(** Changes response. *)
504module Changes_response : sig
505 type t
506
507 val account_id : t -> Id.t
508 val old_state : t -> string
509 val new_state : t -> string
510 val has_more_changes : t -> bool
511 val created : t -> Id.t list
512 val updated : t -> Id.t list
513 val destroyed : t -> Id.t list
514end
515
516(** {1 JSONABLE Interface} *)
517
518(** Module type for types that can be serialized to/from JSON bytes. *)
519module type JSONABLE = sig
520 type t
521
522 val of_string : string -> (t, Error.t) result
523 val to_string : t -> (string, Error.t) result
524end
525
526(** {1 Request Chaining} *)
527
528(** JMAP method chaining with automatic result references.
529
530 This module provides a monadic interface for building JMAP requests
531 where method calls can reference results from previous calls. *)
532module Chain = Chain