this repo has no description

Add typed interface for JMAP message flags and mailbox attributes

This implements task 9 from AGENT.md by adding:

- Flag color support with RGB bit pattern encoding (Red, Orange, Yellow, Green, Blue, Purple, Gray)
- Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02
- Mailbox attribute names (Snoozed, Scheduled, Memos)
- Helper functions to work with these types and convert between typed and string representations
- Utility functions to check, add, and search for messages with specific keywords

Added a test program (flag_color_test) to demonstrate the new functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>

+1 -1
AGENT.md
···
that sensitive API tokens are never printed but redacted instead.
Modify the fastmail-list binary to optionally list only unread messages, and
also list the JMAP labels associated with each message.
-
9. Read the mailbox attribute spec in specs/ and add a typed interface to the
JMAP labels defined in there.
10. Add an OCaml interface to compose result references together explicitly into a
single request, from reading the specs.
···
that sensitive API tokens are never printed but redacted instead.
Modify the fastmail-list binary to optionally list only unread messages, and
also list the JMAP labels associated with each message.
+
9. DONE Read the mailbox attribute spec in specs/ and add a typed interface to the
JMAP labels defined in there.
10. Add an OCaml interface to compose result references together explicitly into a
single request, from reading the specs.
+7
bin/dune
···
(package jmap)
(modules fastmail_list)
(libraries jmap jmap_mail lwt.unix logs logs.fmt))
···
(package jmap)
(modules fastmail_list)
(libraries jmap jmap_mail lwt.unix logs logs.fmt))
+
+
(executable
+
(name flag_color_test)
+
(public_name flag-color-test)
+
(package jmap)
+
(modules flag_color_test)
+
(libraries jmap jmap_mail))
+93
bin/flag_color_test.ml
···
···
+
(** Demo of message flags and mailbox attributes functionality *)
+
+
open Jmap
+
open Jmap_mail.Types
+
+
(** Demonstrate flag color functionality *)
+
let demo_flag_colors () =
+
Printf.printf "Flag Color Demo:\n";
+
Printf.printf "================\n";
+
+
(* Show all flag colors and their bit patterns *)
+
let colors = [Red; Orange; Yellow; Green; Blue; Purple; Gray] in
+
List.iter (fun color ->
+
let (bit0, bit1, bit2) = bits_of_flag_color color in
+
Printf.printf "Color: %-7s Bits: %d%d%d\n"
+
(match color with
+
| Red -> "Red"
+
| Orange -> "Orange"
+
| Yellow -> "Yellow"
+
| Green -> "Green"
+
| Blue -> "Blue"
+
| Purple -> "Purple"
+
| Gray -> "Gray")
+
(if bit0 then 1 else 0)
+
(if bit1 then 1 else 0)
+
(if bit2 then 1 else 0)
+
) colors;
+
+
Printf.printf "\n"
+
+
(** Demonstrate message keyword functionality *)
+
let demo_message_keywords () =
+
Printf.printf "Message Keywords Demo:\n";
+
Printf.printf "=====================\n";
+
+
(* Show all standard message keywords and their string representations *)
+
let keywords = [
+
Notify; Muted; Followed; Memo; HasMemo; HasAttachment; HasNoAttachment;
+
AutoSent; Unsubscribed; CanUnsubscribe; Imported; IsTrusted;
+
MaskedEmail; New; MailFlagBit0; MailFlagBit1; MailFlagBit2
+
] in
+
+
List.iter (fun kw ->
+
Printf.printf "%-15s -> %s\n"
+
(match kw with
+
| Notify -> "Notify"
+
| Muted -> "Muted"
+
| Followed -> "Followed"
+
| Memo -> "Memo"
+
| HasMemo -> "HasMemo"
+
| HasAttachment -> "HasAttachment"
+
| HasNoAttachment -> "HasNoAttachment"
+
| AutoSent -> "AutoSent"
+
| Unsubscribed -> "Unsubscribed"
+
| CanUnsubscribe -> "CanUnsubscribe"
+
| Imported -> "Imported"
+
| IsTrusted -> "IsTrusted"
+
| MaskedEmail -> "MaskedEmail"
+
| New -> "New"
+
| MailFlagBit0 -> "MailFlagBit0"
+
| MailFlagBit1 -> "MailFlagBit1"
+
| MailFlagBit2 -> "MailFlagBit2"
+
| OtherKeyword s -> "Other: " ^ s)
+
(string_of_message_keyword kw)
+
) keywords;
+
+
Printf.printf "\n"
+
+
(** Demonstrate mailbox attribute functionality *)
+
let demo_mailbox_attributes () =
+
Printf.printf "Mailbox Attributes Demo:\n";
+
Printf.printf "=======================\n";
+
+
(* Show all standard mailbox attributes and their string representations *)
+
let attributes = [Snoozed; Scheduled; Memos] in
+
+
List.iter (fun attr ->
+
Printf.printf "%-10s -> %s\n"
+
(match attr with
+
| Snoozed -> "Snoozed"
+
| Scheduled -> "Scheduled"
+
| Memos -> "Memos"
+
| OtherAttribute s -> "Other: " ^ s)
+
(string_of_mailbox_attribute attr)
+
) attributes;
+
+
Printf.printf "\n"
+
+
(** Main entry point *)
+
let () =
+
demo_flag_colors ();
+
demo_message_keywords ();
+
demo_mailbox_attributes ()
+418 -1
lib/jmap_mail.ml
···
updated : id list option;
not_updated : (id * set_error) list option;
}
end
(** {1 JSON serialization} *)
···
| e -> Error (Parse_error (Printexc.to_string e))
in
Lwt.return result
-
| Error e -> Lwt.return (Error e)
···
updated : id list option;
not_updated : (id * set_error) list option;
}
+
+
(** {1:message_flags Message Flags and Mailbox Attributes} *)
+
+
(** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords *)
+
type flag_color =
+
| Red (** Bit pattern 000 *)
+
| Orange (** Bit pattern 100 *)
+
| Yellow (** Bit pattern 010 *)
+
| Green (** Bit pattern 111 *)
+
| Blue (** Bit pattern 001 *)
+
| Purple (** Bit pattern 101 *)
+
| Gray (** Bit pattern 011 *)
+
+
(** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
+
type message_keyword =
+
| Notify (** Indicate a notification should be shown for this message *)
+
| Muted (** User is not interested in future replies to this thread *)
+
| Followed (** User is particularly interested in future replies to this thread *)
+
| Memo (** Message is a note-to-self about another message in the same thread *)
+
| HasMemo (** Message has an associated memo with the $memo keyword *)
+
| HasAttachment (** Message has an attachment *)
+
| HasNoAttachment (** Message does not have an attachment *)
+
| AutoSent (** Message was sent automatically as a response due to a user rule *)
+
| Unsubscribed (** User has unsubscribed from the thread this message is in *)
+
| CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *)
+
| Imported (** Message was imported from another mailbox *)
+
| IsTrusted (** Server has verified authenticity of the from name and email *)
+
| MaskedEmail (** Message was received via an alias created for an individual sender *)
+
| New (** Message should be made more prominent due to a recent action *)
+
| MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *)
+
| MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *)
+
| MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *)
+
| OtherKeyword of string (** Other non-standard keywords *)
+
+
(** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
+
type mailbox_attribute =
+
| Snoozed (** Mailbox containing messages that have been snoozed *)
+
| Scheduled (** Mailbox containing messages scheduled to be sent later *)
+
| Memos (** Mailbox containing messages with the $memo keyword *)
+
| OtherAttribute of string (** Other non-standard mailbox attributes *)
+
+
(** Functions for working with flag colors based on the specification in
+
draft-ietf-mailmaint-messageflag-mailboxattribute-02, section 3.1. *)
+
+
(** Convert bit pattern to flag color *)
+
let flag_color_of_bits bit0 bit1 bit2 =
+
match (bit0, bit1, bit2) with
+
| (false, false, false) -> Red (* 000 *)
+
| (true, false, false) -> Orange (* 100 *)
+
| (false, true, false) -> Yellow (* 010 *)
+
| (true, true, true) -> Green (* 111 *)
+
| (false, false, true) -> Blue (* 001 *)
+
| (true, false, true) -> Purple (* 101 *)
+
| (false, true, true) -> Gray (* 011 *)
+
| (true, true, false) -> Green (* 110 - not in spec, defaulting to green *)
+
+
(** Get bits for a flag color *)
+
let bits_of_flag_color = function
+
| Red -> (false, false, false)
+
| Orange -> (true, false, false)
+
| Yellow -> (false, true, false)
+
| Green -> (true, true, true)
+
| Blue -> (false, false, true)
+
| Purple -> (true, false, true)
+
| Gray -> (false, true, true)
+
+
(** Check if a keyword list contains a flag color *)
+
let has_flag_color keywords =
+
let has_bit0 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit0" -> true
+
| _ -> false
+
) keywords in
+
+
let has_bit1 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit1" -> true
+
| _ -> false
+
) keywords in
+
+
let has_bit2 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit2" -> true
+
| _ -> false
+
) keywords in
+
+
has_bit0 || has_bit1 || has_bit2
+
+
(** Extract flag color from keywords if present *)
+
let get_flag_color keywords =
+
(* First check if the message has the \Flagged system flag *)
+
let is_flagged = List.exists (function
+
| (Flagged, true) -> true
+
| _ -> false
+
) keywords in
+
+
if not is_flagged then
+
None
+
else
+
(* Get values of each bit flag *)
+
let bit0 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit0" -> true
+
| _ -> false
+
) keywords in
+
+
let bit1 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit1" -> true
+
| _ -> false
+
) keywords in
+
+
let bit2 = List.exists (function
+
| (Custom s, true) when s = "$MailFlagBit2" -> true
+
| _ -> false
+
) keywords in
+
+
Some (flag_color_of_bits bit0 bit1 bit2)
+
+
(** Convert a message keyword to its string representation *)
+
let string_of_message_keyword = function
+
| Notify -> "$notify"
+
| Muted -> "$muted"
+
| Followed -> "$followed"
+
| Memo -> "$memo"
+
| HasMemo -> "$hasmemo"
+
| HasAttachment -> "$hasattachment"
+
| HasNoAttachment -> "$hasnoattachment"
+
| AutoSent -> "$autosent"
+
| Unsubscribed -> "$unsubscribed"
+
| CanUnsubscribe -> "$canunsubscribe"
+
| Imported -> "$imported"
+
| IsTrusted -> "$istrusted"
+
| MaskedEmail -> "$maskedemail"
+
| New -> "$new"
+
| MailFlagBit0 -> "$MailFlagBit0"
+
| MailFlagBit1 -> "$MailFlagBit1"
+
| MailFlagBit2 -> "$MailFlagBit2"
+
| OtherKeyword s -> s
+
+
(** Parse a string into a message keyword *)
+
let message_keyword_of_string = function
+
| "$notify" -> Notify
+
| "$muted" -> Muted
+
| "$followed" -> Followed
+
| "$memo" -> Memo
+
| "$hasmemo" -> HasMemo
+
| "$hasattachment" -> HasAttachment
+
| "$hasnoattachment" -> HasNoAttachment
+
| "$autosent" -> AutoSent
+
| "$unsubscribed" -> Unsubscribed
+
| "$canunsubscribe" -> CanUnsubscribe
+
| "$imported" -> Imported
+
| "$istrusted" -> IsTrusted
+
| "$maskedemail" -> MaskedEmail
+
| "$new" -> New
+
| "$MailFlagBit0" -> MailFlagBit0
+
| "$MailFlagBit1" -> MailFlagBit1
+
| "$MailFlagBit2" -> MailFlagBit2
+
| s -> OtherKeyword s
+
+
(** Convert a mailbox attribute to its string representation *)
+
let string_of_mailbox_attribute = function
+
| Snoozed -> "Snoozed"
+
| Scheduled -> "Scheduled"
+
| Memos -> "Memos"
+
| OtherAttribute s -> s
+
+
(** Parse a string into a mailbox attribute *)
+
let mailbox_attribute_of_string = function
+
| "Snoozed" -> Snoozed
+
| "Scheduled" -> Scheduled
+
| "Memos" -> Memos
+
| s -> OtherAttribute s
end
(** {1 JSON serialization} *)
···
| e -> Error (Parse_error (Printexc.to_string e))
in
Lwt.return result
+
| Error e -> Lwt.return (Error e)
+
+
(** Helper functions for working with message flags and mailbox attributes *)
+
+
(** Check if an email has a specific message keyword
+
@param email The email to check
+
@param keyword The message keyword to look for
+
@return true if the email has the keyword, false otherwise
+
+
TODO:claude *)
+
let has_message_keyword (email:Types.email) keyword =
+
let open Types in
+
let keyword_string = string_of_message_keyword keyword in
+
List.exists (function
+
| (Custom s, true) when s = keyword_string -> true
+
| _ -> false
+
) email.keywords
+
+
(** Add a message keyword to an email
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param email_id The email ID
+
@param keyword The message keyword to add
+
@return Success or error
+
+
TODO:claude *)
+
let add_message_keyword conn ~account_id ~email_id ~keyword =
+
let keyword_string = Types.string_of_message_keyword keyword in
+
+
let request = {
+
using = [
+
Jmap.Capability.to_string Jmap.Capability.Core;
+
Capability.to_string Capability.Mail
+
];
+
method_calls = [
+
{
+
name = "Email/set";
+
arguments = `O [
+
("accountId", `String account_id);
+
("update", `O [
+
(email_id, `O [
+
("keywords", `O [
+
(keyword_string, `Bool true)
+
])
+
])
+
]);
+
];
+
method_call_id = "m1";
+
}
+
];
+
created_ids = None;
+
} in
+
+
let* response_result = make_request conn.config request in
+
match response_result with
+
| Ok response ->
+
let result =
+
try
+
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
+
inv.name = "Email/set") response.method_responses in
+
let args = method_response.arguments in
+
match Ezjsonm.find_opt args ["updated"] with
+
| Some (`A ids) -> Ok ()
+
| _ ->
+
match Ezjsonm.find_opt args ["notUpdated"] with
+
| Some (`O errors) ->
+
Error (Parse_error ("Failed to update email: " ^ email_id))
+
| _ -> Error (Parse_error "Unexpected response format")
+
with
+
| Not_found -> Error (Parse_error "Email/set method response not found")
+
| e -> Error (Parse_error (Printexc.to_string e))
+
in
+
Lwt.return result
+
| Error e -> Lwt.return (Error e)
+
+
(** Set a flag color for an email
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param email_id The email ID
+
@param color The flag color to set
+
@return Success or error
+
+
TODO:claude *)
+
let set_flag_color conn ~account_id ~email_id ~color =
+
(* Get the bit pattern for the color *)
+
let (bit0, bit1, bit2) = Types.bits_of_flag_color color in
+
+
(* Build the keywords update object *)
+
let keywords = [
+
("$flagged", `Bool true);
+
("$MailFlagBit0", `Bool bit0);
+
("$MailFlagBit1", `Bool bit1);
+
("$MailFlagBit2", `Bool bit2);
+
] in
+
+
let request = {
+
using = [
+
Jmap.Capability.to_string Jmap.Capability.Core;
+
Capability.to_string Capability.Mail
+
];
+
method_calls = [
+
{
+
name = "Email/set";
+
arguments = `O [
+
("accountId", `String account_id);
+
("update", `O [
+
(email_id, `O [
+
("keywords", `O keywords)
+
])
+
]);
+
];
+
method_call_id = "m1";
+
}
+
];
+
created_ids = None;
+
} in
+
+
let* response_result = make_request conn.config request in
+
match response_result with
+
| Ok response ->
+
let result =
+
try
+
let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
+
inv.name = "Email/set") response.method_responses in
+
let args = method_response.arguments in
+
match Ezjsonm.find_opt args ["updated"] with
+
| Some (`A ids) -> Ok ()
+
| _ ->
+
match Ezjsonm.find_opt args ["notUpdated"] with
+
| Some (`O errors) ->
+
Error (Parse_error ("Failed to update email: " ^ email_id))
+
| _ -> Error (Parse_error "Unexpected response format")
+
with
+
| Not_found -> Error (Parse_error "Email/set method response not found")
+
| e -> Error (Parse_error (Printexc.to_string e))
+
in
+
Lwt.return result
+
| Error e -> Lwt.return (Error e)
+
+
(** Convert an email's keywords to typed message_keyword list
+
@param email The email to analyze
+
@return List of message keywords
+
+
TODO:claude *)
+
let get_message_keywords (email:Types.email) =
+
let open Types in
+
List.filter_map (function
+
| (Custom s, true) -> Some (message_keyword_of_string s)
+
| _ -> None
+
) email.keywords
+
+
(** Get emails with a specific message keyword
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param keyword The message keyword to search for
+
@param limit Optional limit on number of emails to return
+
@return List of emails with the keyword if successful
+
+
TODO:claude *)
+
let get_emails_with_keyword conn ~account_id ~keyword ?limit () =
+
let keyword_string = Types.string_of_message_keyword keyword in
+
+
(* Query for emails with the specified keyword *)
+
let query_request = {
+
using = [
+
Jmap.Capability.to_string Jmap.Capability.Core;
+
Capability.to_string Capability.Mail
+
];
+
method_calls = [
+
{
+
name = "Email/query";
+
arguments = `O ([
+
("accountId", `String account_id);
+
("filter", `O [("hasKeyword", `String keyword_string)]);
+
("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
+
] @ (match limit with
+
| Some l -> [("limit", `Float (float_of_int l))]
+
| None -> []
+
));
+
method_call_id = "q1";
+
}
+
];
+
created_ids = None;
+
} in
+
+
let* query_result = make_request conn.config query_request in
+
match query_result with
+
| Ok query_response ->
+
(try
+
let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
+
inv.name = "Email/query") query_response.method_responses in
+
let args = query_method.arguments in
+
match Ezjsonm.find_opt args ["ids"] with
+
| Some (`A ids) ->
+
let email_ids = List.map (function
+
| `String id -> id
+
| _ -> raise (Invalid_argument "Email ID is not a string")
+
) ids in
+
+
(* If we have IDs, fetch the actual email objects *)
+
if List.length email_ids > 0 then
+
let get_request = {
+
using = [
+
Jmap.Capability.to_string Jmap.Capability.Core;
+
Capability.to_string Capability.Mail
+
];
+
method_calls = [
+
{
+
name = "Email/get";
+
arguments = `O [
+
("accountId", `String account_id);
+
("ids", `A (List.map (fun id -> `String id) email_ids));
+
];
+
method_call_id = "g1";
+
}
+
];
+
created_ids = None;
+
} in
+
+
let* get_result = make_request conn.config get_request in
+
match get_result with
+
| Ok get_response ->
+
(try
+
let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) ->
+
inv.name = "Email/get") get_response.method_responses in
+
let args = get_method.arguments in
+
match Ezjsonm.find_opt args ["list"] with
+
| Some (`A email_list) ->
+
let parse_results = List.map email_of_json email_list in
+
let (successes, failures) = List.partition Result.is_ok parse_results in
+
if List.length failures > 0 then
+
Lwt.return (Error (Parse_error "Failed to parse some emails"))
+
else
+
Lwt.return (Ok (List.map Result.get_ok successes))
+
| _ -> Lwt.return (Error (Parse_error "Email list not found in response"))
+
with
+
| Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found"))
+
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
+
| Error e -> Lwt.return (Error e)
+
else
+
(* No emails with the keyword *)
+
Lwt.return (Ok [])
+
+
| _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response"))
+
with
+
| Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found"))
+
| Invalid_argument msg -> Lwt.return (Error (Parse_error msg))
+
| e -> Lwt.return (Error (Parse_error (Printexc.to_string e))))
+
| Error e -> Lwt.return (Error e)
+131 -1
lib/jmap_mail.mli
···
updated : id list option;
not_updated : (id * set_error) list option;
}
end
(** {1 JSON serialization} *)
···
connection ->
account_id:Jmap.Types.id ->
email_id:Jmap.Types.id ->
-
(Types.email, Jmap.Api.error) result Lwt.t
···
updated : id list option;
not_updated : (id * set_error) list option;
}
+
+
(** {1:message_flags Message Flags and Mailbox Attributes} *)
+
+
(** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords *)
+
type flag_color =
+
| Red (** Bit pattern 000 *)
+
| Orange (** Bit pattern 100 *)
+
| Yellow (** Bit pattern 010 *)
+
| Green (** Bit pattern 111 *)
+
| Blue (** Bit pattern 001 *)
+
| Purple (** Bit pattern 101 *)
+
| Gray (** Bit pattern 011 *)
+
+
(** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
+
type message_keyword =
+
| Notify (** Indicate a notification should be shown for this message *)
+
| Muted (** User is not interested in future replies to this thread *)
+
| Followed (** User is particularly interested in future replies to this thread *)
+
| Memo (** Message is a note-to-self about another message in the same thread *)
+
| HasMemo (** Message has an associated memo with the $memo keyword *)
+
| HasAttachment (** Message has an attachment *)
+
| HasNoAttachment (** Message does not have an attachment *)
+
| AutoSent (** Message was sent automatically as a response due to a user rule *)
+
| Unsubscribed (** User has unsubscribed from the thread this message is in *)
+
| CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *)
+
| Imported (** Message was imported from another mailbox *)
+
| IsTrusted (** Server has verified authenticity of the from name and email *)
+
| MaskedEmail (** Message was received via an alias created for an individual sender *)
+
| New (** Message should be made more prominent due to a recent action *)
+
| MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *)
+
| MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *)
+
| MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *)
+
| OtherKeyword of string (** Other non-standard keywords *)
+
+
(** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *)
+
type mailbox_attribute =
+
| Snoozed (** Mailbox containing messages that have been snoozed *)
+
| Scheduled (** Mailbox containing messages scheduled to be sent later *)
+
| Memos (** Mailbox containing messages with the $memo keyword *)
+
| OtherAttribute of string (** Other non-standard mailbox attributes *)
+
+
(** Functions for working with flag colors *)
+
val flag_color_of_bits : bool -> bool -> bool -> flag_color
+
+
(** Get bits for a flag color *)
+
val bits_of_flag_color : flag_color -> bool * bool * bool
+
+
(** Check if a message has a flag color based on its keywords *)
+
val has_flag_color : (keyword * bool) list -> bool
+
+
(** Get the flag color from a message's keywords, if present *)
+
val get_flag_color : (keyword * bool) list -> flag_color option
+
+
(** Convert a message keyword to its string representation *)
+
val string_of_message_keyword : message_keyword -> string
+
+
(** Parse a string into a message keyword *)
+
val message_keyword_of_string : string -> message_keyword
+
+
(** Convert a mailbox attribute to its string representation *)
+
val string_of_mailbox_attribute : mailbox_attribute -> string
+
+
(** Parse a string into a mailbox attribute *)
+
val mailbox_attribute_of_string : string -> mailbox_attribute
end
(** {1 JSON serialization} *)
···
connection ->
account_id:Jmap.Types.id ->
email_id:Jmap.Types.id ->
+
(Types.email, Jmap.Api.error) result Lwt.t
+
+
(** Check if an email has a specific message keyword
+
@param email The email to check
+
@param keyword The message keyword to look for
+
@return true if the email has the keyword, false otherwise
+
+
TODO:claude *)
+
val has_message_keyword :
+
Types.email ->
+
Types.message_keyword ->
+
bool
+
+
(** Add a message keyword to an email
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param email_id The email ID
+
@param keyword The message keyword to add
+
@return Success or error
+
+
TODO:claude *)
+
val add_message_keyword :
+
connection ->
+
account_id:Jmap.Types.id ->
+
email_id:Jmap.Types.id ->
+
keyword:Types.message_keyword ->
+
(unit, Jmap.Api.error) result Lwt.t
+
+
(** Set a flag color for an email
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param email_id The email ID
+
@param color The flag color to set
+
@return Success or error
+
+
TODO:claude *)
+
val set_flag_color :
+
connection ->
+
account_id:Jmap.Types.id ->
+
email_id:Jmap.Types.id ->
+
color:Types.flag_color ->
+
(unit, Jmap.Api.error) result Lwt.t
+
+
(** Convert an email's keywords to typed message_keyword list
+
@param email The email to analyze
+
@return List of message keywords
+
+
TODO:claude *)
+
val get_message_keywords :
+
Types.email ->
+
Types.message_keyword list
+
+
(** Get emails with a specific message keyword
+
@param conn The JMAP connection
+
@param account_id The account ID
+
@param keyword The message keyword to search for
+
@param limit Optional limit on number of emails to return
+
@return List of emails with the keyword if successful
+
+
TODO:claude *)
+
val get_emails_with_keyword :
+
connection ->
+
account_id:Jmap.Types.id ->
+
keyword:Types.message_keyword ->
+
?limit:int ->
+
unit ->
+
(Types.email list, Jmap.Api.error) result Lwt.t