this repo has no description

Enhance JMAP implementation to better match specifications

- Enhanced Thread management with complete Thread/get and Thread/changes methods
- Improved SearchSnippet module with SearchSnippet/get operation
- Added Email/import, Email/parse, and Email/copy methods
- Added Apple Mail flags and mailbox attributes from draft-ietf-mailmaint-messageflag-mailboxattribute
- Restructured keywords into a proper Keyword module
- Added documentation about handling OCaml docstring spacing in CLAUDE.md

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+61 -2
CLAUDE.md
···
Then examine the HTML docs built for that module. You will see that there are module references with __ in them, e.g. "Jmap__.Jmap_email_types.Email_address.t" which indicate that the module is being accessed directly instead of via the module aliases defined.
-
# Structure Simplification
-
Avoid redundant module nesting. When a file is named after a module (e.g., `jmap_identity.mli`), there's no need to have a matching nested module inside the file (e.g., `module Identity : sig...`). Instead, define types and functions directly at the top level of the file. Also, ensure that submodule main types are always named `t`, not named after the module (e.g., use `Create.t` not `Create.create`).
# Software engineering
···
Then examine the HTML docs built for that module. You will see that there are module references with __ in them, e.g. "Jmap__.Jmap_email_types.Email_address.t" which indicate that the module is being accessed directly instead of via the module aliases defined.
+
## Documentation Comments
+
+
When adding OCaml documentation comments, be careful about ambiguous documentation comments. If you see errors like:
+
+
```
+
Error (warning 50 [unexpected-docstring]): ambiguous documentation comment
+
```
+
+
This usually means there isn't enough whitespace between the documentation comment and the code element it's documenting. Always:
+
+
1. Add blank lines between consecutive documentation comments
+
2. Add a blank line before a documentation comment for a module/type/value declaration
+
3. When documenting record fields or variant constructors, place the comment after the field with at least one space
+
+
Example of correct documentation spacing:
+
+
```ocaml
+
(** Module documentation. *)
+
+
(** Value documentation. *)
+
val some_value : int
+
+
(** Type documentation. *)
+
type t =
+
| First (** First constructor *)
+
| Second (** Second constructor *)
+
+
(** Record documentation. *)
+
type record = {
+
field1 : int; (** Field1 documentation *)
+
field2 : string (** Field2 documentation *)
+
}
+
```
+
+
If in doubt, add more whitespace lines than needed - you can always clean this up later with `dune build @fmt` to get ocamlformat to sort out the whitespace properly.
+
+
# Module Structure Guidelines
+
+
IMPORTANT: For all modules, use a nested module structure with a canonical `type t` inside each submodule. This approach ensures consistent type naming and logical grouping of related functionality.
+
+
1. Top-level files should define their main types directly (e.g., `jmap_identity.mli` should define identity-related types at the top level).
+
+
2. Related operations or specialized subtypes should be defined in nested modules within the file:
+
```ocaml
+
module Create : sig
+
type t (* NOT 'type create' or any other name *)
+
(* Functions operating on creation requests *)
+
+
module Response : sig
+
type t
+
(* Functions for creation responses *)
+
end
+
end
+
```
+
+
3. Consistently use `type t` for the main type in each module and submodule.
+
+
4. Functions operating on a type should be placed in the same module as the type.
+
+
5. When a file is named after a concept (e.g., `jmap_identity.mli`), there's no need to have a matching nested module inside the file (e.g., `module Identity : sig...`), as the file itself represents that namespace.
+
This structured approach promotes encapsulation, consistent type naming, and clearer organization of related functionality.
# Software engineering
+194 -5
jmap-email/jmap_email.mli
···
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.5> RFC 8621, Section 1.5 *)
val push_event_type_email_delivery : string
-
(** JMAP keywords corresponding to IMAP system flags.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
val keyword_draft : string
val keyword_seen : string
val keyword_flagged : string
val keyword_answered : string
-
(** Common JMAP keywords from RFC 5788.
-
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
val keyword_forwarded : string
val keyword_phishing : string
val keyword_junk : string
val keyword_notjunk : string
-
(** Functions to manipulate email flags/keywords *)
module Keyword_ops : sig
(** Add a keyword/flag to an email *)
val add : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
(** Remove a keyword/flag from an email *)
val remove : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
(** Mark an email as seen/read *)
val mark_as_seen : Types.Email.t -> Types.Email.t
···
(** Mark an email as phishing *)
val mark_as_phishing : Types.Email.t -> Types.Email.t
(** Add a custom keyword to an email *)
val add_custom : Types.Email.t -> string -> Types.Email.t
(** Remove a custom keyword from an email *)
val remove_custom : Types.Email.t -> string -> Types.Email.t
(** Create a patch object to add a keyword to emails *)
val add_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
···
(** Create a patch object to mark emails as unseen/unread *)
val mark_unseen_patch : unit -> Jmap.Methods.patch_object
end
(** Conversion functions for JMAP/IMAP compatibility *)
module Conversion : sig
(** Convert a JMAP keyword variant to IMAP flag *)
val keyword_to_imap_flag : Types.Keywords.keyword -> string
(** Convert an IMAP flag to JMAP keyword variant *)
val imap_flag_to_keyword : string -> Types.Keywords.keyword
-
(** Check if a string is valid for use as a custom keyword according to RFC 8621 *)
val is_valid_custom_keyword : string -> bool
(** Get the JMAP protocol string representation of a keyword *)
···
(** Parse a JMAP protocol string into a keyword variant *)
val string_to_keyword : string -> Types.Keywords.keyword
end
(** {1 Helper Functions} *)
···
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.5> RFC 8621, Section 1.5 *)
val push_event_type_email_delivery : string
+
(** Keyword string constants for JMAP email flags.
+
Provides easy access to standardized keyword string values.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
+
module Keyword : sig
+
(** {1 IMAP System Flags} *)
+
+
(** "$draft": The Email is a draft the user is composing *)
+
val draft : string
+
+
(** "$seen": The Email has been read *)
+
val seen : string
+
+
(** "$flagged": The Email has been flagged for urgent/special attention *)
+
val flagged : string
+
+
(** "$answered": The Email has been replied to *)
+
val answered : string
+
+
(** {1 Common Extension Keywords} *)
+
+
(** "$forwarded": The Email has been forwarded *)
+
val forwarded : string
+
+
(** "$phishing": The Email is likely to be phishing *)
+
val phishing : string
+
+
(** "$junk": The Email is spam/junk *)
+
val junk : string
+
+
(** "$notjunk": The Email is explicitly marked as not spam/junk *)
+
val notjunk : string
+
+
(** {1 Apple Mail and Vendor Extensions}
+
@see <https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute/> *)
+
+
(** "$notify": Request to be notified when this email gets a reply *)
+
val notify : string
+
+
(** "$muted": Email is muted (notifications disabled) *)
+
val muted : string
+
+
(** "$followed": Email thread is followed for notifications *)
+
val followed : string
+
+
(** "$memo": Email has a memo/note associated with it *)
+
val memo : string
+
+
(** "$hasmemo": Email has a memo, annotation or note property *)
+
val hasmemo : string
+
+
(** "$autosent": Email was generated or sent automatically *)
+
val autosent : string
+
+
(** "$unsubscribed": User has unsubscribed from this sender *)
+
val unsubscribed : string
+
+
(** "$canunsubscribe": Email contains unsubscribe information *)
+
val canunsubscribe : string
+
+
(** "$imported": Email was imported from another system *)
+
val imported : string
+
+
(** "$istrusted": Email is from a trusted/verified sender *)
+
val istrusted : string
+
+
(** "$maskedemail": Email is to/from a masked/anonymous address *)
+
val maskedemail : string
+
+
(** "$new": Email was recently delivered *)
+
val new_mail : string
+
+
(** {1 Apple Mail Color Flag Bits} *)
+
+
(** "$MailFlagBit0": First color flag bit (red) *)
+
val mailflagbit0 : string
+
+
(** "$MailFlagBit1": Second color flag bit (orange) *)
+
val mailflagbit1 : string
+
+
(** "$MailFlagBit2": Third color flag bit (yellow) *)
+
val mailflagbit2 : string
+
+
(** {1 Color Flag Combinations} *)
+
+
(** Get color flag bit values for a specific color
+
@return A list of flags to set to create the requested color *)
+
val color_flags : [`Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray] -> string list
+
+
(** Check if a string is a valid keyword according to the RFC
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
+
val is_valid : string -> bool
+
end
+
+
(** For backward compatibility - DEPRECATED, use Keyword.draft instead *)
val keyword_draft : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.seen instead *)
val keyword_seen : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.flagged instead *)
val keyword_flagged : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.answered instead *)
val keyword_answered : string
+
(** For backward compatibility - DEPRECATED, use Keyword.forwarded instead *)
val keyword_forwarded : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.phishing instead *)
val keyword_phishing : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.junk instead *)
val keyword_junk : string
+
+
(** For backward compatibility - DEPRECATED, use Keyword.notjunk instead *)
val keyword_notjunk : string
+
(** Email keyword operations.
+
Functions to manipulate and update email keywords/flags. *)
module Keyword_ops : sig
(** Add a keyword/flag to an email *)
val add : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
(** Remove a keyword/flag from an email *)
val remove : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
+
+
(** {1 System Flag Operations} *)
(** Mark an email as seen/read *)
val mark_as_seen : Types.Email.t -> Types.Email.t
···
(** Mark an email as phishing *)
val mark_as_phishing : Types.Email.t -> Types.Email.t
+
(** {1 Extension Flag Operations} *)
+
+
(** Mark an email for notification when replied to *)
+
val mark_as_notify : Types.Email.t -> Types.Email.t
+
+
(** Remove notification flag from an email *)
+
val unmark_notify : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as muted (no notifications) *)
+
val mark_as_muted : Types.Email.t -> Types.Email.t
+
+
(** Unmute an email (allow notifications) *)
+
val unmark_muted : Types.Email.t -> Types.Email.t
+
+
(** Mark an email thread as followed for notifications *)
+
val mark_as_followed : Types.Email.t -> Types.Email.t
+
+
(** Remove followed status from an email thread *)
+
val unmark_followed : Types.Email.t -> Types.Email.t
+
+
(** Mark an email with a memo *)
+
val mark_as_memo : Types.Email.t -> Types.Email.t
+
+
(** Mark an email with the hasmemo flag *)
+
val mark_as_hasmemo : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as automatically sent *)
+
val mark_as_autosent : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as being from an unsubscribed sender *)
+
val mark_as_unsubscribed : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as having unsubscribe capability *)
+
val mark_as_canunsubscribe : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as imported from another system *)
+
val mark_as_imported : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as from a trusted/verified sender *)
+
val mark_as_trusted : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as having masked/anonymous address *)
+
val mark_as_maskedemail : Types.Email.t -> Types.Email.t
+
+
(** Mark an email as new/recent *)
+
val mark_as_new : Types.Email.t -> Types.Email.t
+
+
(** Remove new/recent flag from an email *)
+
val unmark_new : Types.Email.t -> Types.Email.t
+
+
(** {1 Color Flag Operations} *)
+
+
(** Set color flag bits on an email *)
+
val set_color_flags : Types.Email.t -> red:bool -> orange:bool -> yellow:bool -> Types.Email.t
+
+
(** Mark an email with a predefined color *)
+
val mark_as_color : Types.Email.t ->
+
[`Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray] -> Types.Email.t
+
+
(** Remove all color flag bits from an email *)
+
val clear_color_flags : Types.Email.t -> Types.Email.t
+
+
(** {1 Custom Flag Operations} *)
+
(** Add a custom keyword to an email *)
val add_custom : Types.Email.t -> string -> Types.Email.t
(** Remove a custom keyword from an email *)
val remove_custom : Types.Email.t -> string -> Types.Email.t
+
+
(** {1 Patch Object Creation} *)
(** Create a patch object to add a keyword to emails *)
val add_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
···
(** Create a patch object to mark emails as unseen/unread *)
val mark_unseen_patch : unit -> Jmap.Methods.patch_object
+
+
(** Create a patch object to set a specific color on emails *)
+
val set_color_patch : [`Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray] ->
+
Jmap.Methods.patch_object
end
(** Conversion functions for JMAP/IMAP compatibility *)
module Conversion : sig
+
(** {1 Keyword/Flag Conversion} *)
+
(** Convert a JMAP keyword variant to IMAP flag *)
val keyword_to_imap_flag : Types.Keywords.keyword -> string
(** Convert an IMAP flag to JMAP keyword variant *)
val imap_flag_to_keyword : string -> Types.Keywords.keyword
+
(** Check if a string is valid for use as a custom keyword according to RFC 8621.
+
@deprecated Use Keyword.is_valid instead. *)
val is_valid_custom_keyword : string -> bool
(** Get the JMAP protocol string representation of a keyword *)
···
(** Parse a JMAP protocol string into a keyword variant *)
val string_to_keyword : string -> Types.Keywords.keyword
+
+
(** {1 Color Conversion} *)
+
+
(** Convert a color name to the corresponding flag bit combination *)
+
val color_to_flags : [`Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray] ->
+
Types.Keywords.keyword list
+
+
(** Try to determine a color from a set of keywords *)
+
val keywords_to_color : Types.Keywords.t ->
+
[`Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray | `None] option
end
(** {1 Helper Functions} *)
+154 -3
jmap-email/jmap_email_types.mli
···
| Phishing (** "$phishing": The Email is likely to be phishing *)
| Junk (** "$junk": The Email is spam/junk *)
| NotJunk (** "$notjunk": The Email is explicitly marked as not spam/junk *)
| Custom of string (** Arbitrary user-defined keyword *)
(** A set of keywords applied to an email *)
···
val take_id : t -> id
end
-
(** Email import options.
-
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.5> RFC 8621, Section 4.5 *)
type email_import_options = {
import_to_mailboxes : id list;
import_keywords : Keywords.t option;
import_received_at : date option;
}
(** Email copy options.
-
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.6> RFC 8621, Section 4.6 *)
type email_copy_options = {
copy_to_account_id : id;
copy_to_mailboxes : id list;
···
| Phishing (** "$phishing": The Email is likely to be phishing *)
| Junk (** "$junk": The Email is spam/junk *)
| NotJunk (** "$notjunk": The Email is explicitly marked as not spam/junk *)
+
+
(* Apple Mail and other vendor extension keywords from draft-ietf-mailmaint-messageflag-mailboxattribute *)
+
| Notify (** "$notify": Request to be notified when this email gets a reply *)
+
| Muted (** "$muted": Email is muted (notifications disabled) *)
+
| Followed (** "$followed": Email thread is followed for notifications *)
+
| Memo (** "$memo": Email has a memo/note associated with it *)
+
| HasMemo (** "$hasmemo": Email has a memo, annotation or note property *)
+
| Autosent (** "$autosent": Email was generated or sent automatically *)
+
| Unsubscribed (** "$unsubscribed": User has unsubscribed from this sender *)
+
| CanUnsubscribe (** "$canunsubscribe": Email contains unsubscribe information *)
+
| Imported (** "$imported": Email was imported from another system *)
+
| IsTrusted (** "$istrusted": Email is from a trusted/verified sender *)
+
| MaskedEmail (** "$maskedemail": Email is to/from a masked/anonymous address *)
+
| New (** "$new": Email was recently delivered *)
+
+
(* Apple Mail flag colors (color bit flags) *)
+
| MailFlagBit0 (** "$MailFlagBit0": First color flag bit (red) *)
+
| MailFlagBit1 (** "$MailFlagBit1": Second color flag bit (orange) *)
+
| MailFlagBit2 (** "$MailFlagBit2": Third color flag bit (yellow) *)
| Custom of string (** Arbitrary user-defined keyword *)
(** A set of keywords applied to an email *)
···
val take_id : t -> id
end
+
(** Email/import method arguments and responses.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.8> RFC 8621, Section 4.8 *)
+
module Import : sig
+
(** Arguments for Email/import method *)
+
type args = {
+
account_id : id;
+
blob_ids : id list;
+
mailbox_ids : id id_map;
+
keywords : Keywords.t option;
+
received_at : date option;
+
}
+
+
(** Create import arguments *)
+
val create_args :
+
account_id:id ->
+
blob_ids:id list ->
+
mailbox_ids:id id_map ->
+
?keywords:Keywords.t ->
+
?received_at:date ->
+
unit -> args
+
+
(** Response for a single imported email *)
+
type email_import_result = {
+
blob_id : id;
+
email : Email.t;
+
}
+
+
(** Create an email import result *)
+
val create_result :
+
blob_id:id ->
+
email:Email.t ->
+
unit -> email_import_result
+
+
(** Response for Email/import method *)
+
type response = {
+
account_id : id;
+
created : email_import_result id_map;
+
not_created : Jmap.Error.Set_error.t id_map;
+
}
+
+
(** Create import response *)
+
val create_response :
+
account_id:id ->
+
created:email_import_result id_map ->
+
not_created:Jmap.Error.Set_error.t id_map ->
+
unit -> response
+
end
+
+
(** Email/parse method arguments and responses.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.9> RFC 8621, Section 4.9 *)
+
module Parse : sig
+
(** Arguments for Email/parse method *)
+
type args = {
+
account_id : id;
+
blob_ids : id list;
+
properties : string list option;
+
}
+
+
(** Create parse arguments *)
+
val create_args :
+
account_id:id ->
+
blob_ids:id list ->
+
?properties:string list ->
+
unit -> args
+
+
(** Response for a single parsed email *)
+
type email_parse_result = {
+
blob_id : id;
+
parsed : Email.t;
+
}
+
+
(** Create an email parse result *)
+
val create_result :
+
blob_id:id ->
+
parsed:Email.t ->
+
unit -> email_parse_result
+
+
(** Response for Email/parse method *)
+
type response = {
+
account_id : id;
+
parsed : email_parse_result id_map;
+
not_parsed : string id_map;
+
}
+
+
(** Create parse response *)
+
val create_response :
+
account_id:id ->
+
parsed:email_parse_result id_map ->
+
not_parsed:string id_map ->
+
unit -> response
+
end
+
+
(** Email import options.
+
@deprecated Use Import.args instead.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.8> RFC 8621, Section 4.8 *)
type email_import_options = {
import_to_mailboxes : id list;
import_keywords : Keywords.t option;
import_received_at : date option;
}
+
(** Email/copy method arguments and responses.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.7> RFC 8621, Section 4.7 *)
+
module Copy : sig
+
(** Arguments for Email/copy method *)
+
type args = {
+
from_account_id : id;
+
account_id : id;
+
create : (id * id id_map) id_map;
+
on_success_destroy_original : bool option;
+
destroy_from_if_in_state : string option;
+
}
+
+
(** Create copy arguments *)
+
val create_args :
+
from_account_id:id ->
+
account_id:id ->
+
create:(id * id id_map) id_map ->
+
?on_success_destroy_original:bool ->
+
?destroy_from_if_in_state:string ->
+
unit -> args
+
+
(** Response for Email/copy method *)
+
type response = {
+
from_account_id : id;
+
account_id : id;
+
created : Email.t id_map option;
+
not_created : Jmap.Error.Set_error.t id_map option;
+
}
+
+
(** Create copy response *)
+
val create_response :
+
from_account_id:id ->
+
account_id:id ->
+
?created:Email.t id_map ->
+
?not_created:Jmap.Error.Set_error.t id_map ->
+
unit -> response
+
end
+
(** Email copy options.
+
@deprecated Use Copy.args instead.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.7> RFC 8621, Section 4.7 *)
type email_copy_options = {
copy_to_account_id : id;
copy_to_mailboxes : id list;
+4
jmap-email/jmap_mailbox.mli
···
| Trash (** Messages that have been deleted *)
| Junk (** Messages determined to be spam *)
| Important (** Messages deemed important *)
| Other of string (** Custom or non-standard role *)
| None (** No specific role assigned *)
···
| Trash (** Messages that have been deleted *)
| Junk (** Messages determined to be spam *)
| Important (** Messages deemed important *)
+
| Snoozed (** Messages snoozed for later notification/reappearance, from draft-ietf-mailmaint-messageflag-mailboxattribute *)
+
| Scheduled (** Messages scheduled for sending at a later time, from draft-ietf-mailmaint-messageflag-mailboxattribute *)
+
| Memos (** Messages containing memos or notes, from draft-ietf-mailmaint-messageflag-mailboxattribute *)
+
| Other of string (** Custom or non-standard role *)
| None (** No specific role assigned *)
+84 -6
jmap-email/jmap_search_snippet.mli
···
(** JMAP Search Snippet.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
(** SearchSnippet object.
-
Note: Does not have an 'id' property.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
-
type t = {
-
email_id : Jmap.Types.id;
-
subject : string option;
-
preview : string option;
-
}
···
(** JMAP Search Snippet.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
+
open Jmap.Types
+
open Jmap.Methods
+
(** SearchSnippet object.
+
Provides highlighted snippets of emails matching search criteria.
+
Note: Does not have an 'id' property; the key is the emailId.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
+
module SearchSnippet : sig
+
type t
+
+
(** Get the email ID this snippet is for *)
+
val email_id : t -> id
+
+
(** Get the highlighted subject snippet (if matched) *)
+
val subject : t -> string option
+
+
(** Get the highlighted preview snippet (if matched) *)
+
val preview : t -> string option
+
+
(** Create a new SearchSnippet object *)
+
val v :
+
email_id:id ->
+
?subject:string ->
+
?preview:string ->
+
unit -> t
+
end
+
+
(** {1 SearchSnippet Methods} *)
+
+
(** Arguments for SearchSnippet/get.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5.1> RFC 8621, Section 5.1 *)
+
module Get_args : sig
+
type t
+
+
(** The account ID *)
+
val account_id : t -> id
+
+
(** The filter to use for the search *)
+
val filter : t -> Filter.t
+
+
(** Email IDs to return snippets for. If null, all matching emails are included *)
+
val email_ids : t -> id list option
+
+
(** Creation arguments *)
+
val v :
+
account_id:id ->
+
filter:Filter.t ->
+
?email_ids:id list ->
+
unit -> t
+
end
+
+
(** Response for SearchSnippet/get.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5.1> RFC 8621, Section 5.1 *)
+
module Get_response : sig
+
type t
+
+
(** The account ID *)
+
val account_id : t -> id
+
+
(** The search state string (for caching) *)
+
val list : t -> SearchSnippet.t id_map
+
+
(** IDs requested that weren't found *)
+
val not_found : t -> id list
+
+
(** Creation *)
+
val v :
+
account_id:id ->
+
list:SearchSnippet.t id_map ->
+
not_found:id list ->
+
unit -> t
+
end
+
+
(** {1 Helper Functions} *)
+
+
(** Helper to extract all matched keywords from a snippet.
+
This parses highlighted portions from the snippet to get the actual search terms. *)
+
val extract_matched_terms : string -> string list
+
+
(** Helper to create a filter that searches in email body text.
+
This is commonly used for SearchSnippet/get requests. *)
+
val create_body_text_filter : string -> Filter.t
+
+
(** Helper to create a filter that searches across multiple email fields.
+
This searches subject, body, and headers for the given text. *)
+
val create_fulltext_filter : string -> Filter.t
+116
jmap-email/jmap_thread.mli
···
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
open Jmap.Types
(** Thread object.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
module Thread : sig
type t
val id : t -> id
val email_ids : t -> id list
val v : id:id -> email_ids:id list -> t
end
···
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
open Jmap.Types
+
open Jmap.Methods
(** Thread object.
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
module Thread : sig
type t
+
(** Get the thread ID (server-set, immutable) *)
val id : t -> id
+
+
(** Get the IDs of emails in the thread (server-set) *)
val email_ids : t -> id list
+
(** Create a new Thread object *)
val v : id:id -> email_ids:id list -> t
end
+
+
(** Thread properties that can be requested in Thread/get.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3.1> RFC 8621, Section 3.1 *)
+
type property =
+
| Id (** The Thread id *)
+
| EmailIds (** The list of email IDs in the Thread *)
+
+
(** Convert a property variant to its string representation *)
+
val property_to_string : property -> string
+
+
(** Parse a string into a property variant *)
+
val string_to_property : string -> property
+
+
(** Get a list of all standard Thread properties *)
+
val all_properties : property list
+
+
(** {1 Thread Methods} *)
+
+
(** Arguments for Thread/get - extends standard get arguments.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3.1> RFC 8621, Section 3.1 *)
+
module Get_args : sig
+
type t
+
+
val account_id : t -> id
+
val ids : t -> id list option
+
val properties : t -> string list option
+
+
val v :
+
account_id:id ->
+
?ids:id list ->
+
?properties:string list ->
+
unit -> t
+
end
+
+
(** Response for Thread/get - extends standard get response.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3.1> RFC 8621, Section 3.1 *)
+
module Get_response : sig
+
type t
+
+
val account_id : t -> id
+
val state : t -> string
+
val list : t -> Thread.t list
+
val not_found : t -> id list
+
+
val v :
+
account_id:id ->
+
state:string ->
+
list:Thread.t list ->
+
not_found:id list ->
+
unit -> t
+
end
+
+
(** Arguments for Thread/changes - extends standard changes arguments.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3.2> RFC 8621, Section 3.2 *)
+
module Changes_args : sig
+
type t
+
+
val account_id : t -> id
+
val since_state : t -> string
+
val max_changes : t -> uint option
+
+
val v :
+
account_id:id ->
+
since_state:string ->
+
?max_changes:uint ->
+
unit -> t
+
end
+
+
(** Response for Thread/changes - extends standard changes response.
+
@see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3.2> RFC 8621, Section 3.2 *)
+
module Changes_response : sig
+
type t
+
+
val account_id : t -> id
+
val old_state : t -> string
+
val new_state : t -> string
+
val has_more_changes : t -> bool
+
val created : t -> id list
+
val updated : t -> id list
+
val destroyed : t -> id list
+
+
val v :
+
account_id:id ->
+
old_state:string ->
+
new_state:string ->
+
has_more_changes:bool ->
+
created:id list ->
+
updated:id list ->
+
destroyed:id list ->
+
unit -> t
+
end
+
+
(** {1 Helper Functions} *)
+
+
(** Create a filter to find threads with specific email ID *)
+
val filter_has_email : id -> Filter.t
+
+
(** Create a filter to find threads with emails from a specific sender *)
+
val filter_from : string -> Filter.t
+
+
(** Create a filter to find threads with emails to a specific recipient *)
+
val filter_to : string -> Filter.t
+
+
(** Create a filter to find threads with specific subject *)
+
val filter_subject : string -> Filter.t
+
+
(** Create a filter to find threads with emails received before a date *)
+
val filter_before : date -> Filter.t
+
+
(** Create a filter to find threads with emails received after a date *)
+
val filter_after : date -> Filter.t