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
··· 42 42 that sensitive API tokens are never printed but redacted instead. 43 43 Modify the fastmail-list binary to optionally list only unread messages, and 44 44 also list the JMAP labels associated with each message. 45 - 9. Read the mailbox attribute spec in specs/ and add a typed interface to the 45 + 9. DONE Read the mailbox attribute spec in specs/ and add a typed interface to the 46 46 JMAP labels defined in there. 47 47 10. Add an OCaml interface to compose result references together explicitly into a 48 48 single request, from reading the specs.
+7
bin/dune
··· 4 4 (package jmap) 5 5 (modules fastmail_list) 6 6 (libraries jmap jmap_mail lwt.unix logs logs.fmt)) 7 + 8 + (executable 9 + (name flag_color_test) 10 + (public_name flag-color-test) 11 + (package jmap) 12 + (modules flag_color_test) 13 + (libraries jmap jmap_mail))
+93
bin/flag_color_test.ml
··· 1 + (** Demo of message flags and mailbox attributes functionality *) 2 + 3 + open Jmap 4 + open Jmap_mail.Types 5 + 6 + (** Demonstrate flag color functionality *) 7 + let demo_flag_colors () = 8 + Printf.printf "Flag Color Demo:\n"; 9 + Printf.printf "================\n"; 10 + 11 + (* Show all flag colors and their bit patterns *) 12 + let colors = [Red; Orange; Yellow; Green; Blue; Purple; Gray] in 13 + List.iter (fun color -> 14 + let (bit0, bit1, bit2) = bits_of_flag_color color in 15 + Printf.printf "Color: %-7s Bits: %d%d%d\n" 16 + (match color with 17 + | Red -> "Red" 18 + | Orange -> "Orange" 19 + | Yellow -> "Yellow" 20 + | Green -> "Green" 21 + | Blue -> "Blue" 22 + | Purple -> "Purple" 23 + | Gray -> "Gray") 24 + (if bit0 then 1 else 0) 25 + (if bit1 then 1 else 0) 26 + (if bit2 then 1 else 0) 27 + ) colors; 28 + 29 + Printf.printf "\n" 30 + 31 + (** Demonstrate message keyword functionality *) 32 + let demo_message_keywords () = 33 + Printf.printf "Message Keywords Demo:\n"; 34 + Printf.printf "=====================\n"; 35 + 36 + (* Show all standard message keywords and their string representations *) 37 + let keywords = [ 38 + Notify; Muted; Followed; Memo; HasMemo; HasAttachment; HasNoAttachment; 39 + AutoSent; Unsubscribed; CanUnsubscribe; Imported; IsTrusted; 40 + MaskedEmail; New; MailFlagBit0; MailFlagBit1; MailFlagBit2 41 + ] in 42 + 43 + List.iter (fun kw -> 44 + Printf.printf "%-15s -> %s\n" 45 + (match kw with 46 + | Notify -> "Notify" 47 + | Muted -> "Muted" 48 + | Followed -> "Followed" 49 + | Memo -> "Memo" 50 + | HasMemo -> "HasMemo" 51 + | HasAttachment -> "HasAttachment" 52 + | HasNoAttachment -> "HasNoAttachment" 53 + | AutoSent -> "AutoSent" 54 + | Unsubscribed -> "Unsubscribed" 55 + | CanUnsubscribe -> "CanUnsubscribe" 56 + | Imported -> "Imported" 57 + | IsTrusted -> "IsTrusted" 58 + | MaskedEmail -> "MaskedEmail" 59 + | New -> "New" 60 + | MailFlagBit0 -> "MailFlagBit0" 61 + | MailFlagBit1 -> "MailFlagBit1" 62 + | MailFlagBit2 -> "MailFlagBit2" 63 + | OtherKeyword s -> "Other: " ^ s) 64 + (string_of_message_keyword kw) 65 + ) keywords; 66 + 67 + Printf.printf "\n" 68 + 69 + (** Demonstrate mailbox attribute functionality *) 70 + let demo_mailbox_attributes () = 71 + Printf.printf "Mailbox Attributes Demo:\n"; 72 + Printf.printf "=======================\n"; 73 + 74 + (* Show all standard mailbox attributes and their string representations *) 75 + let attributes = [Snoozed; Scheduled; Memos] in 76 + 77 + List.iter (fun attr -> 78 + Printf.printf "%-10s -> %s\n" 79 + (match attr with 80 + | Snoozed -> "Snoozed" 81 + | Scheduled -> "Scheduled" 82 + | Memos -> "Memos" 83 + | OtherAttribute s -> "Other: " ^ s) 84 + (string_of_mailbox_attribute attr) 85 + ) attributes; 86 + 87 + Printf.printf "\n" 88 + 89 + (** Main entry point *) 90 + let () = 91 + demo_flag_colors (); 92 + demo_message_keywords (); 93 + demo_mailbox_attributes ()
+418 -1
lib/jmap_mail.ml
··· 856 856 updated : id list option; 857 857 not_updated : (id * set_error) list option; 858 858 } 859 + 860 + (** {1:message_flags Message Flags and Mailbox Attributes} *) 861 + 862 + (** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords *) 863 + type flag_color = 864 + | Red (** Bit pattern 000 *) 865 + | Orange (** Bit pattern 100 *) 866 + | Yellow (** Bit pattern 010 *) 867 + | Green (** Bit pattern 111 *) 868 + | Blue (** Bit pattern 001 *) 869 + | Purple (** Bit pattern 101 *) 870 + | Gray (** Bit pattern 011 *) 871 + 872 + (** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *) 873 + type message_keyword = 874 + | Notify (** Indicate a notification should be shown for this message *) 875 + | Muted (** User is not interested in future replies to this thread *) 876 + | Followed (** User is particularly interested in future replies to this thread *) 877 + | Memo (** Message is a note-to-self about another message in the same thread *) 878 + | HasMemo (** Message has an associated memo with the $memo keyword *) 879 + | HasAttachment (** Message has an attachment *) 880 + | HasNoAttachment (** Message does not have an attachment *) 881 + | AutoSent (** Message was sent automatically as a response due to a user rule *) 882 + | Unsubscribed (** User has unsubscribed from the thread this message is in *) 883 + | CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *) 884 + | Imported (** Message was imported from another mailbox *) 885 + | IsTrusted (** Server has verified authenticity of the from name and email *) 886 + | MaskedEmail (** Message was received via an alias created for an individual sender *) 887 + | New (** Message should be made more prominent due to a recent action *) 888 + | MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *) 889 + | MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *) 890 + | MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *) 891 + | OtherKeyword of string (** Other non-standard keywords *) 892 + 893 + (** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *) 894 + type mailbox_attribute = 895 + | Snoozed (** Mailbox containing messages that have been snoozed *) 896 + | Scheduled (** Mailbox containing messages scheduled to be sent later *) 897 + | Memos (** Mailbox containing messages with the $memo keyword *) 898 + | OtherAttribute of string (** Other non-standard mailbox attributes *) 899 + 900 + (** Functions for working with flag colors based on the specification in 901 + draft-ietf-mailmaint-messageflag-mailboxattribute-02, section 3.1. *) 902 + 903 + (** Convert bit pattern to flag color *) 904 + let flag_color_of_bits bit0 bit1 bit2 = 905 + match (bit0, bit1, bit2) with 906 + | (false, false, false) -> Red (* 000 *) 907 + | (true, false, false) -> Orange (* 100 *) 908 + | (false, true, false) -> Yellow (* 010 *) 909 + | (true, true, true) -> Green (* 111 *) 910 + | (false, false, true) -> Blue (* 001 *) 911 + | (true, false, true) -> Purple (* 101 *) 912 + | (false, true, true) -> Gray (* 011 *) 913 + | (true, true, false) -> Green (* 110 - not in spec, defaulting to green *) 914 + 915 + (** Get bits for a flag color *) 916 + let bits_of_flag_color = function 917 + | Red -> (false, false, false) 918 + | Orange -> (true, false, false) 919 + | Yellow -> (false, true, false) 920 + | Green -> (true, true, true) 921 + | Blue -> (false, false, true) 922 + | Purple -> (true, false, true) 923 + | Gray -> (false, true, true) 924 + 925 + (** Check if a keyword list contains a flag color *) 926 + let has_flag_color keywords = 927 + let has_bit0 = List.exists (function 928 + | (Custom s, true) when s = "$MailFlagBit0" -> true 929 + | _ -> false 930 + ) keywords in 931 + 932 + let has_bit1 = List.exists (function 933 + | (Custom s, true) when s = "$MailFlagBit1" -> true 934 + | _ -> false 935 + ) keywords in 936 + 937 + let has_bit2 = List.exists (function 938 + | (Custom s, true) when s = "$MailFlagBit2" -> true 939 + | _ -> false 940 + ) keywords in 941 + 942 + has_bit0 || has_bit1 || has_bit2 943 + 944 + (** Extract flag color from keywords if present *) 945 + let get_flag_color keywords = 946 + (* First check if the message has the \Flagged system flag *) 947 + let is_flagged = List.exists (function 948 + | (Flagged, true) -> true 949 + | _ -> false 950 + ) keywords in 951 + 952 + if not is_flagged then 953 + None 954 + else 955 + (* Get values of each bit flag *) 956 + let bit0 = List.exists (function 957 + | (Custom s, true) when s = "$MailFlagBit0" -> true 958 + | _ -> false 959 + ) keywords in 960 + 961 + let bit1 = List.exists (function 962 + | (Custom s, true) when s = "$MailFlagBit1" -> true 963 + | _ -> false 964 + ) keywords in 965 + 966 + let bit2 = List.exists (function 967 + | (Custom s, true) when s = "$MailFlagBit2" -> true 968 + | _ -> false 969 + ) keywords in 970 + 971 + Some (flag_color_of_bits bit0 bit1 bit2) 972 + 973 + (** Convert a message keyword to its string representation *) 974 + let string_of_message_keyword = function 975 + | Notify -> "$notify" 976 + | Muted -> "$muted" 977 + | Followed -> "$followed" 978 + | Memo -> "$memo" 979 + | HasMemo -> "$hasmemo" 980 + | HasAttachment -> "$hasattachment" 981 + | HasNoAttachment -> "$hasnoattachment" 982 + | AutoSent -> "$autosent" 983 + | Unsubscribed -> "$unsubscribed" 984 + | CanUnsubscribe -> "$canunsubscribe" 985 + | Imported -> "$imported" 986 + | IsTrusted -> "$istrusted" 987 + | MaskedEmail -> "$maskedemail" 988 + | New -> "$new" 989 + | MailFlagBit0 -> "$MailFlagBit0" 990 + | MailFlagBit1 -> "$MailFlagBit1" 991 + | MailFlagBit2 -> "$MailFlagBit2" 992 + | OtherKeyword s -> s 993 + 994 + (** Parse a string into a message keyword *) 995 + let message_keyword_of_string = function 996 + | "$notify" -> Notify 997 + | "$muted" -> Muted 998 + | "$followed" -> Followed 999 + | "$memo" -> Memo 1000 + | "$hasmemo" -> HasMemo 1001 + | "$hasattachment" -> HasAttachment 1002 + | "$hasnoattachment" -> HasNoAttachment 1003 + | "$autosent" -> AutoSent 1004 + | "$unsubscribed" -> Unsubscribed 1005 + | "$canunsubscribe" -> CanUnsubscribe 1006 + | "$imported" -> Imported 1007 + | "$istrusted" -> IsTrusted 1008 + | "$maskedemail" -> MaskedEmail 1009 + | "$new" -> New 1010 + | "$MailFlagBit0" -> MailFlagBit0 1011 + | "$MailFlagBit1" -> MailFlagBit1 1012 + | "$MailFlagBit2" -> MailFlagBit2 1013 + | s -> OtherKeyword s 1014 + 1015 + (** Convert a mailbox attribute to its string representation *) 1016 + let string_of_mailbox_attribute = function 1017 + | Snoozed -> "Snoozed" 1018 + | Scheduled -> "Scheduled" 1019 + | Memos -> "Memos" 1020 + | OtherAttribute s -> s 1021 + 1022 + (** Parse a string into a mailbox attribute *) 1023 + let mailbox_attribute_of_string = function 1024 + | "Snoozed" -> Snoozed 1025 + | "Scheduled" -> Scheduled 1026 + | "Memos" -> Memos 1027 + | s -> OtherAttribute s 859 1028 end 860 1029 861 1030 (** {1 JSON serialization} *) ··· 1478 1647 | e -> Error (Parse_error (Printexc.to_string e)) 1479 1648 in 1480 1649 Lwt.return result 1481 - | Error e -> Lwt.return (Error e) 1650 + | Error e -> Lwt.return (Error e) 1651 + 1652 + (** Helper functions for working with message flags and mailbox attributes *) 1653 + 1654 + (** Check if an email has a specific message keyword 1655 + @param email The email to check 1656 + @param keyword The message keyword to look for 1657 + @return true if the email has the keyword, false otherwise 1658 + 1659 + TODO:claude *) 1660 + let has_message_keyword (email:Types.email) keyword = 1661 + let open Types in 1662 + let keyword_string = string_of_message_keyword keyword in 1663 + List.exists (function 1664 + | (Custom s, true) when s = keyword_string -> true 1665 + | _ -> false 1666 + ) email.keywords 1667 + 1668 + (** Add a message keyword to an email 1669 + @param conn The JMAP connection 1670 + @param account_id The account ID 1671 + @param email_id The email ID 1672 + @param keyword The message keyword to add 1673 + @return Success or error 1674 + 1675 + TODO:claude *) 1676 + let add_message_keyword conn ~account_id ~email_id ~keyword = 1677 + let keyword_string = Types.string_of_message_keyword keyword in 1678 + 1679 + let request = { 1680 + using = [ 1681 + Jmap.Capability.to_string Jmap.Capability.Core; 1682 + Capability.to_string Capability.Mail 1683 + ]; 1684 + method_calls = [ 1685 + { 1686 + name = "Email/set"; 1687 + arguments = `O [ 1688 + ("accountId", `String account_id); 1689 + ("update", `O [ 1690 + (email_id, `O [ 1691 + ("keywords", `O [ 1692 + (keyword_string, `Bool true) 1693 + ]) 1694 + ]) 1695 + ]); 1696 + ]; 1697 + method_call_id = "m1"; 1698 + } 1699 + ]; 1700 + created_ids = None; 1701 + } in 1702 + 1703 + let* response_result = make_request conn.config request in 1704 + match response_result with 1705 + | Ok response -> 1706 + let result = 1707 + try 1708 + let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1709 + inv.name = "Email/set") response.method_responses in 1710 + let args = method_response.arguments in 1711 + match Ezjsonm.find_opt args ["updated"] with 1712 + | Some (`A ids) -> Ok () 1713 + | _ -> 1714 + match Ezjsonm.find_opt args ["notUpdated"] with 1715 + | Some (`O errors) -> 1716 + Error (Parse_error ("Failed to update email: " ^ email_id)) 1717 + | _ -> Error (Parse_error "Unexpected response format") 1718 + with 1719 + | Not_found -> Error (Parse_error "Email/set method response not found") 1720 + | e -> Error (Parse_error (Printexc.to_string e)) 1721 + in 1722 + Lwt.return result 1723 + | Error e -> Lwt.return (Error e) 1724 + 1725 + (** Set a flag color for an email 1726 + @param conn The JMAP connection 1727 + @param account_id The account ID 1728 + @param email_id The email ID 1729 + @param color The flag color to set 1730 + @return Success or error 1731 + 1732 + TODO:claude *) 1733 + let set_flag_color conn ~account_id ~email_id ~color = 1734 + (* Get the bit pattern for the color *) 1735 + let (bit0, bit1, bit2) = Types.bits_of_flag_color color in 1736 + 1737 + (* Build the keywords update object *) 1738 + let keywords = [ 1739 + ("$flagged", `Bool true); 1740 + ("$MailFlagBit0", `Bool bit0); 1741 + ("$MailFlagBit1", `Bool bit1); 1742 + ("$MailFlagBit2", `Bool bit2); 1743 + ] in 1744 + 1745 + let request = { 1746 + using = [ 1747 + Jmap.Capability.to_string Jmap.Capability.Core; 1748 + Capability.to_string Capability.Mail 1749 + ]; 1750 + method_calls = [ 1751 + { 1752 + name = "Email/set"; 1753 + arguments = `O [ 1754 + ("accountId", `String account_id); 1755 + ("update", `O [ 1756 + (email_id, `O [ 1757 + ("keywords", `O keywords) 1758 + ]) 1759 + ]); 1760 + ]; 1761 + method_call_id = "m1"; 1762 + } 1763 + ]; 1764 + created_ids = None; 1765 + } in 1766 + 1767 + let* response_result = make_request conn.config request in 1768 + match response_result with 1769 + | Ok response -> 1770 + let result = 1771 + try 1772 + let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1773 + inv.name = "Email/set") response.method_responses in 1774 + let args = method_response.arguments in 1775 + match Ezjsonm.find_opt args ["updated"] with 1776 + | Some (`A ids) -> Ok () 1777 + | _ -> 1778 + match Ezjsonm.find_opt args ["notUpdated"] with 1779 + | Some (`O errors) -> 1780 + Error (Parse_error ("Failed to update email: " ^ email_id)) 1781 + | _ -> Error (Parse_error "Unexpected response format") 1782 + with 1783 + | Not_found -> Error (Parse_error "Email/set method response not found") 1784 + | e -> Error (Parse_error (Printexc.to_string e)) 1785 + in 1786 + Lwt.return result 1787 + | Error e -> Lwt.return (Error e) 1788 + 1789 + (** Convert an email's keywords to typed message_keyword list 1790 + @param email The email to analyze 1791 + @return List of message keywords 1792 + 1793 + TODO:claude *) 1794 + let get_message_keywords (email:Types.email) = 1795 + let open Types in 1796 + List.filter_map (function 1797 + | (Custom s, true) -> Some (message_keyword_of_string s) 1798 + | _ -> None 1799 + ) email.keywords 1800 + 1801 + (** Get emails with a specific message keyword 1802 + @param conn The JMAP connection 1803 + @param account_id The account ID 1804 + @param keyword The message keyword to search for 1805 + @param limit Optional limit on number of emails to return 1806 + @return List of emails with the keyword if successful 1807 + 1808 + TODO:claude *) 1809 + let get_emails_with_keyword conn ~account_id ~keyword ?limit () = 1810 + let keyword_string = Types.string_of_message_keyword keyword in 1811 + 1812 + (* Query for emails with the specified keyword *) 1813 + let query_request = { 1814 + using = [ 1815 + Jmap.Capability.to_string Jmap.Capability.Core; 1816 + Capability.to_string Capability.Mail 1817 + ]; 1818 + method_calls = [ 1819 + { 1820 + name = "Email/query"; 1821 + arguments = `O ([ 1822 + ("accountId", `String account_id); 1823 + ("filter", `O [("hasKeyword", `String keyword_string)]); 1824 + ("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]); 1825 + ] @ (match limit with 1826 + | Some l -> [("limit", `Float (float_of_int l))] 1827 + | None -> [] 1828 + )); 1829 + method_call_id = "q1"; 1830 + } 1831 + ]; 1832 + created_ids = None; 1833 + } in 1834 + 1835 + let* query_result = make_request conn.config query_request in 1836 + match query_result with 1837 + | Ok query_response -> 1838 + (try 1839 + let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1840 + inv.name = "Email/query") query_response.method_responses in 1841 + let args = query_method.arguments in 1842 + match Ezjsonm.find_opt args ["ids"] with 1843 + | Some (`A ids) -> 1844 + let email_ids = List.map (function 1845 + | `String id -> id 1846 + | _ -> raise (Invalid_argument "Email ID is not a string") 1847 + ) ids in 1848 + 1849 + (* If we have IDs, fetch the actual email objects *) 1850 + if List.length email_ids > 0 then 1851 + let get_request = { 1852 + using = [ 1853 + Jmap.Capability.to_string Jmap.Capability.Core; 1854 + Capability.to_string Capability.Mail 1855 + ]; 1856 + method_calls = [ 1857 + { 1858 + name = "Email/get"; 1859 + arguments = `O [ 1860 + ("accountId", `String account_id); 1861 + ("ids", `A (List.map (fun id -> `String id) email_ids)); 1862 + ]; 1863 + method_call_id = "g1"; 1864 + } 1865 + ]; 1866 + created_ids = None; 1867 + } in 1868 + 1869 + let* get_result = make_request conn.config get_request in 1870 + match get_result with 1871 + | Ok get_response -> 1872 + (try 1873 + let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1874 + inv.name = "Email/get") get_response.method_responses in 1875 + let args = get_method.arguments in 1876 + match Ezjsonm.find_opt args ["list"] with 1877 + | Some (`A email_list) -> 1878 + let parse_results = List.map email_of_json email_list in 1879 + let (successes, failures) = List.partition Result.is_ok parse_results in 1880 + if List.length failures > 0 then 1881 + Lwt.return (Error (Parse_error "Failed to parse some emails")) 1882 + else 1883 + Lwt.return (Ok (List.map Result.get_ok successes)) 1884 + | _ -> Lwt.return (Error (Parse_error "Email list not found in response")) 1885 + with 1886 + | Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found")) 1887 + | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1888 + | Error e -> Lwt.return (Error e) 1889 + else 1890 + (* No emails with the keyword *) 1891 + Lwt.return (Ok []) 1892 + 1893 + | _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response")) 1894 + with 1895 + | Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found")) 1896 + | Invalid_argument msg -> Lwt.return (Error (Parse_error msg)) 1897 + | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1898 + | Error e -> Lwt.return (Error e)
+131 -1
lib/jmap_mail.mli
··· 845 845 updated : id list option; 846 846 not_updated : (id * set_error) list option; 847 847 } 848 + 849 + (** {1:message_flags Message Flags and Mailbox Attributes} *) 850 + 851 + (** Flag color defined by the combination of MailFlagBit0, MailFlagBit1, and MailFlagBit2 keywords *) 852 + type flag_color = 853 + | Red (** Bit pattern 000 *) 854 + | Orange (** Bit pattern 100 *) 855 + | Yellow (** Bit pattern 010 *) 856 + | Green (** Bit pattern 111 *) 857 + | Blue (** Bit pattern 001 *) 858 + | Purple (** Bit pattern 101 *) 859 + | Gray (** Bit pattern 011 *) 860 + 861 + (** Standard message keywords as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *) 862 + type message_keyword = 863 + | Notify (** Indicate a notification should be shown for this message *) 864 + | Muted (** User is not interested in future replies to this thread *) 865 + | Followed (** User is particularly interested in future replies to this thread *) 866 + | Memo (** Message is a note-to-self about another message in the same thread *) 867 + | HasMemo (** Message has an associated memo with the $memo keyword *) 868 + | HasAttachment (** Message has an attachment *) 869 + | HasNoAttachment (** Message does not have an attachment *) 870 + | AutoSent (** Message was sent automatically as a response due to a user rule *) 871 + | Unsubscribed (** User has unsubscribed from the thread this message is in *) 872 + | CanUnsubscribe (** Message has an RFC8058-compliant List-Unsubscribe header *) 873 + | Imported (** Message was imported from another mailbox *) 874 + | IsTrusted (** Server has verified authenticity of the from name and email *) 875 + | MaskedEmail (** Message was received via an alias created for an individual sender *) 876 + | New (** Message should be made more prominent due to a recent action *) 877 + | MailFlagBit0 (** Bit 0 of the 3-bit flag color pattern *) 878 + | MailFlagBit1 (** Bit 1 of the 3-bit flag color pattern *) 879 + | MailFlagBit2 (** Bit 2 of the 3-bit flag color pattern *) 880 + | OtherKeyword of string (** Other non-standard keywords *) 881 + 882 + (** Special mailbox attribute names as defined in draft-ietf-mailmaint-messageflag-mailboxattribute-02 *) 883 + type mailbox_attribute = 884 + | Snoozed (** Mailbox containing messages that have been snoozed *) 885 + | Scheduled (** Mailbox containing messages scheduled to be sent later *) 886 + | Memos (** Mailbox containing messages with the $memo keyword *) 887 + | OtherAttribute of string (** Other non-standard mailbox attributes *) 888 + 889 + (** Functions for working with flag colors *) 890 + val flag_color_of_bits : bool -> bool -> bool -> flag_color 891 + 892 + (** Get bits for a flag color *) 893 + val bits_of_flag_color : flag_color -> bool * bool * bool 894 + 895 + (** Check if a message has a flag color based on its keywords *) 896 + val has_flag_color : (keyword * bool) list -> bool 897 + 898 + (** Get the flag color from a message's keywords, if present *) 899 + val get_flag_color : (keyword * bool) list -> flag_color option 900 + 901 + (** Convert a message keyword to its string representation *) 902 + val string_of_message_keyword : message_keyword -> string 903 + 904 + (** Parse a string into a message keyword *) 905 + val message_keyword_of_string : string -> message_keyword 906 + 907 + (** Convert a mailbox attribute to its string representation *) 908 + val string_of_mailbox_attribute : mailbox_attribute -> string 909 + 910 + (** Parse a string into a mailbox attribute *) 911 + val mailbox_attribute_of_string : string -> mailbox_attribute 848 912 end 849 913 850 914 (** {1 JSON serialization} *) ··· 962 1026 connection -> 963 1027 account_id:Jmap.Types.id -> 964 1028 email_id:Jmap.Types.id -> 965 - (Types.email, Jmap.Api.error) result Lwt.t 1029 + (Types.email, Jmap.Api.error) result Lwt.t 1030 + 1031 + (** Check if an email has a specific message keyword 1032 + @param email The email to check 1033 + @param keyword The message keyword to look for 1034 + @return true if the email has the keyword, false otherwise 1035 + 1036 + TODO:claude *) 1037 + val has_message_keyword : 1038 + Types.email -> 1039 + Types.message_keyword -> 1040 + bool 1041 + 1042 + (** Add a message keyword to an email 1043 + @param conn The JMAP connection 1044 + @param account_id The account ID 1045 + @param email_id The email ID 1046 + @param keyword The message keyword to add 1047 + @return Success or error 1048 + 1049 + TODO:claude *) 1050 + val add_message_keyword : 1051 + connection -> 1052 + account_id:Jmap.Types.id -> 1053 + email_id:Jmap.Types.id -> 1054 + keyword:Types.message_keyword -> 1055 + (unit, Jmap.Api.error) result Lwt.t 1056 + 1057 + (** Set a flag color for an email 1058 + @param conn The JMAP connection 1059 + @param account_id The account ID 1060 + @param email_id The email ID 1061 + @param color The flag color to set 1062 + @return Success or error 1063 + 1064 + TODO:claude *) 1065 + val set_flag_color : 1066 + connection -> 1067 + account_id:Jmap.Types.id -> 1068 + email_id:Jmap.Types.id -> 1069 + color:Types.flag_color -> 1070 + (unit, Jmap.Api.error) result Lwt.t 1071 + 1072 + (** Convert an email's keywords to typed message_keyword list 1073 + @param email The email to analyze 1074 + @return List of message keywords 1075 + 1076 + TODO:claude *) 1077 + val get_message_keywords : 1078 + Types.email -> 1079 + Types.message_keyword list 1080 + 1081 + (** Get emails with a specific message keyword 1082 + @param conn The JMAP connection 1083 + @param account_id The account ID 1084 + @param keyword The message keyword to search for 1085 + @param limit Optional limit on number of emails to return 1086 + @return List of emails with the keyword if successful 1087 + 1088 + TODO:claude *) 1089 + val get_emails_with_keyword : 1090 + connection -> 1091 + account_id:Jmap.Types.id -> 1092 + keyword:Types.message_keyword -> 1093 + ?limit:int -> 1094 + unit -> 1095 + (Types.email list, Jmap.Api.error) result Lwt.t