this repo has no description
1(** Implementation of the JMAP Mail extension, as defined in RFC8621 *) 2 3module Types = struct 4 open Jmap.Types 5 6 (** {1 Mail capabilities} *) 7 8 (** Capability URI for JMAP Mail*) 9 let capability_mail = "urn:ietf:params:jmap:mail" 10 11 (** Capability URI for JMAP Submission *) 12 let capability_submission = "urn:ietf:params:jmap:submission" 13 14 (** Capability URI for JMAP Vacation Response *) 15 let capability_vacation_response = "urn:ietf:params:jmap:vacationresponse" 16 17 (** {1:mailbox Mailbox objects} *) 18 19 (** A role for a mailbox. See RFC8621 Section 2. *) 20 type mailbox_role = 21 | All (** All mail *) 22 | Archive (** Archived mail *) 23 | Drafts (** Draft messages *) 24 | Flagged (** Starred/flagged mail *) 25 | Important (** Important mail *) 26 | Inbox (** Inbox *) 27 | Junk (** Spam/Junk mail *) 28 | Sent (** Sent mail *) 29 | Trash (** Deleted/Trash mail *) 30 | Unknown of string (** Server-specific roles *) 31 32 (** A mailbox (folder) in a mail account. See RFC8621 Section 2. *) 33 type mailbox = { 34 id : id; 35 name : string; 36 parent_id : id option; 37 role : mailbox_role option; 38 sort_order : unsigned_int; 39 total_emails : unsigned_int; 40 unread_emails : unsigned_int; 41 total_threads : unsigned_int; 42 unread_threads : unsigned_int; 43 is_subscribed : bool; 44 my_rights : mailbox_rights; 45 } 46 47 (** Rights for a mailbox. See RFC8621 Section 2. *) 48 and mailbox_rights = { 49 may_read_items : bool; 50 may_add_items : bool; 51 may_remove_items : bool; 52 may_set_seen : bool; 53 may_set_keywords : bool; 54 may_create_child : bool; 55 may_rename : bool; 56 may_delete : bool; 57 may_submit : bool; 58 } 59 60 (** Filter condition for mailbox queries. See RFC8621 Section 2.3. *) 61 type mailbox_filter_condition = { 62 parent_id : id option; 63 name : string option; 64 role : string option; 65 has_any_role : bool option; 66 is_subscribed : bool option; 67 } 68 69 type mailbox_query_filter = [ 70 | `And of mailbox_query_filter list 71 | `Or of mailbox_query_filter list 72 | `Not of mailbox_query_filter 73 | `Condition of mailbox_filter_condition 74 ] 75 76 (** Mailbox/get request arguments. See RFC8621 Section 2.1. *) 77 type mailbox_get_arguments = { 78 account_id : id; 79 ids : id list option; 80 properties : string list option; 81 } 82 83 (** Mailbox/get response. See RFC8621 Section 2.1. *) 84 type mailbox_get_response = { 85 account_id : id; 86 state : string; 87 list : mailbox list; 88 not_found : id list; 89 } 90 91 (** Mailbox/changes request arguments. See RFC8621 Section 2.2. *) 92 type mailbox_changes_arguments = { 93 account_id : id; 94 since_state : string; 95 max_changes : unsigned_int option; 96 } 97 98 (** Mailbox/changes response. See RFC8621 Section 2.2. *) 99 type mailbox_changes_response = { 100 account_id : id; 101 old_state : string; 102 new_state : string; 103 has_more_changes : bool; 104 created : id list; 105 updated : id list; 106 destroyed : id list; 107 } 108 109 (** Mailbox/query request arguments. See RFC8621 Section 2.3. *) 110 type mailbox_query_arguments = { 111 account_id : id; 112 filter : mailbox_query_filter option; 113 sort : [ `name | `role | `sort_order ] list option; 114 limit : unsigned_int option; 115 } 116 117 (** Mailbox/query response. See RFC8621 Section 2.3. *) 118 type mailbox_query_response = { 119 account_id : id; 120 query_state : string; 121 can_calculate_changes : bool; 122 position : unsigned_int; 123 ids : id list; 124 total : unsigned_int option; 125 } 126 127 (** Mailbox/queryChanges request arguments. See RFC8621 Section 2.4. *) 128 type mailbox_query_changes_arguments = { 129 account_id : id; 130 filter : mailbox_query_filter option; 131 sort : [ `name | `role | `sort_order ] list option; 132 since_query_state : string; 133 max_changes : unsigned_int option; 134 up_to_id : id option; 135 } 136 137 (** Mailbox/queryChanges response. See RFC8621 Section 2.4. *) 138 type mailbox_query_changes_response = { 139 account_id : id; 140 old_query_state : string; 141 new_query_state : string; 142 total : unsigned_int option; 143 removed : id list; 144 added : mailbox_query_changes_added list; 145 } 146 147 and mailbox_query_changes_added = { 148 id : id; 149 index : unsigned_int; 150 } 151 152 (** Mailbox/set request arguments. See RFC8621 Section 2.5. *) 153 type mailbox_set_arguments = { 154 account_id : id; 155 if_in_state : string option; 156 create : (id * mailbox_creation) list option; 157 update : (id * mailbox_update) list option; 158 destroy : id list option; 159 } 160 161 and mailbox_creation = { 162 name : string; 163 parent_id : id option; 164 role : string option; 165 sort_order : unsigned_int option; 166 is_subscribed : bool option; 167 } 168 169 and mailbox_update = { 170 name : string option; 171 parent_id : id option; 172 role : string option; 173 sort_order : unsigned_int option; 174 is_subscribed : bool option; 175 } 176 177 (** Mailbox/set response. See RFC8621 Section 2.5. *) 178 type mailbox_set_response = { 179 account_id : id; 180 old_state : string option; 181 new_state : string; 182 created : (id * mailbox) list option; 183 updated : id list option; 184 destroyed : id list option; 185 not_created : (id * set_error) list option; 186 not_updated : (id * set_error) list option; 187 not_destroyed : (id * set_error) list option; 188 } 189 190 (** {1:thread Thread objects} *) 191 192 (** A thread in a mail account. See RFC8621 Section 3. *) 193 type thread = { 194 id : id; 195 email_ids : id list; 196 } 197 198 (** Thread/get request arguments. See RFC8621 Section 3.1. *) 199 type thread_get_arguments = { 200 account_id : id; 201 ids : id list option; 202 properties : string list option; 203 } 204 205 (** Thread/get response. See RFC8621 Section 3.1. *) 206 type thread_get_response = { 207 account_id : id; 208 state : string; 209 list : thread list; 210 not_found : id list; 211 } 212 213 (** Thread/changes request arguments. See RFC8621 Section 3.2. *) 214 type thread_changes_arguments = { 215 account_id : id; 216 since_state : string; 217 max_changes : unsigned_int option; 218 } 219 220 (** Thread/changes response. See RFC8621 Section 3.2. *) 221 type thread_changes_response = { 222 account_id : id; 223 old_state : string; 224 new_state : string; 225 has_more_changes : bool; 226 created : id list; 227 updated : id list; 228 destroyed : id list; 229 } 230 231 (** {1:email Email objects} *) 232 233 (** Addressing (mailbox) information. See RFC8621 Section 4.1.1. *) 234 type email_address = { 235 name : string option; 236 email : string; 237 parameters : (string * string) list; 238 } 239 240 (** Message header field. See RFC8621 Section 4.1.2. *) 241 type header = { 242 name : string; 243 value : string; 244 } 245 246 (** Email keyword (flag). See RFC8621 Section 4.3. *) 247 type keyword = 248 | Flagged 249 | Answered 250 | Draft 251 | Forwarded 252 | Phishing 253 | Junk 254 | NotJunk 255 | Seen 256 | Unread 257 | Custom of string 258 259 (** Email message. See RFC8621 Section 4. *) 260 type email = { 261 id : id; 262 blob_id : id; 263 thread_id : id; 264 mailbox_ids : (id * bool) list; 265 keywords : (keyword * bool) list; 266 size : unsigned_int; 267 received_at : utc_date; 268 message_id : string list; 269 in_reply_to : string list option; 270 references : string list option; 271 sender : email_address list option; 272 from : email_address list option; 273 to_ : email_address list option; 274 cc : email_address list option; 275 bcc : email_address list option; 276 reply_to : email_address list option; 277 subject : string option; 278 sent_at : utc_date option; 279 has_attachment : bool option; 280 preview : string option; 281 body_values : (string * string) list option; 282 text_body : email_body_part list option; 283 html_body : email_body_part list option; 284 attachments : email_body_part list option; 285 headers : header list option; 286 } 287 288 (** Email body part. See RFC8621 Section 4.1.4. *) 289 and email_body_part = { 290 part_id : string option; 291 blob_id : id option; 292 size : unsigned_int option; 293 headers : header list option; 294 name : string option; 295 type_ : string option; 296 charset : string option; 297 disposition : string option; 298 cid : string option; 299 language : string list option; 300 location : string option; 301 sub_parts : email_body_part list option; 302 header_parameter_name : string option; 303 header_parameter_value : string option; 304 } 305 306 (** Email query filter condition. See RFC8621 Section 4.4. *) 307 type email_filter_condition = { 308 in_mailbox : id option; 309 in_mailbox_other_than : id list option; 310 min_size : unsigned_int option; 311 max_size : unsigned_int option; 312 before : utc_date option; 313 after : utc_date option; 314 header : (string * string) option; 315 from : string option; 316 to_ : string option; 317 cc : string option; 318 bcc : string option; 319 subject : string option; 320 body : string option; 321 has_keyword : string option; 322 not_keyword : string option; 323 has_attachment : bool option; 324 text : string option; 325 } 326 327 type email_query_filter = [ 328 | `And of email_query_filter list 329 | `Or of email_query_filter list 330 | `Not of email_query_filter 331 | `Condition of email_filter_condition 332 ] 333 334 (** Email/get request arguments. See RFC8621 Section 4.5. *) 335 type email_get_arguments = { 336 account_id : id; 337 ids : id list option; 338 properties : string list option; 339 body_properties : string list option; 340 fetch_text_body_values : bool option; 341 fetch_html_body_values : bool option; 342 fetch_all_body_values : bool option; 343 max_body_value_bytes : unsigned_int option; 344 } 345 346 (** Email/get response. See RFC8621 Section 4.5. *) 347 type email_get_response = { 348 account_id : id; 349 state : string; 350 list : email list; 351 not_found : id list; 352 } 353 354 (** Email/changes request arguments. See RFC8621 Section 4.6. *) 355 type email_changes_arguments = { 356 account_id : id; 357 since_state : string; 358 max_changes : unsigned_int option; 359 } 360 361 (** Email/changes response. See RFC8621 Section 4.6. *) 362 type email_changes_response = { 363 account_id : id; 364 old_state : string; 365 new_state : string; 366 has_more_changes : bool; 367 created : id list; 368 updated : id list; 369 destroyed : id list; 370 } 371 372 (** Email/query request arguments. See RFC8621 Section 4.4. *) 373 type email_query_arguments = { 374 account_id : id; 375 filter : email_query_filter option; 376 sort : comparator list option; 377 collapse_threads : bool option; 378 position : unsigned_int option; 379 anchor : id option; 380 anchor_offset : int_t option; 381 limit : unsigned_int option; 382 calculate_total : bool option; 383 } 384 385 (** Email/query response. See RFC8621 Section 4.4. *) 386 type email_query_response = { 387 account_id : id; 388 query_state : string; 389 can_calculate_changes : bool; 390 position : unsigned_int; 391 ids : id list; 392 total : unsigned_int option; 393 thread_ids : id list option; 394 } 395 396 (** Email/queryChanges request arguments. See RFC8621 Section 4.7. *) 397 type email_query_changes_arguments = { 398 account_id : id; 399 filter : email_query_filter option; 400 sort : comparator list option; 401 collapse_threads : bool option; 402 since_query_state : string; 403 max_changes : unsigned_int option; 404 up_to_id : id option; 405 } 406 407 (** Email/queryChanges response. See RFC8621 Section 4.7. *) 408 type email_query_changes_response = { 409 account_id : id; 410 old_query_state : string; 411 new_query_state : string; 412 total : unsigned_int option; 413 removed : id list; 414 added : email_query_changes_added list; 415 } 416 417 and email_query_changes_added = { 418 id : id; 419 index : unsigned_int; 420 } 421 422 (** Email/set request arguments. See RFC8621 Section 4.8. *) 423 type email_set_arguments = { 424 account_id : id; 425 if_in_state : string option; 426 create : (id * email_creation) list option; 427 update : (id * email_update) list option; 428 destroy : id list option; 429 } 430 431 and email_creation = { 432 mailbox_ids : (id * bool) list; 433 keywords : (keyword * bool) list option; 434 received_at : utc_date option; 435 message_id : string list option; 436 in_reply_to : string list option; 437 references : string list option; 438 sender : email_address list option; 439 from : email_address list option; 440 to_ : email_address list option; 441 cc : email_address list option; 442 bcc : email_address list option; 443 reply_to : email_address list option; 444 subject : string option; 445 body_values : (string * string) list option; 446 text_body : email_body_part list option; 447 html_body : email_body_part list option; 448 attachments : email_body_part list option; 449 headers : header list option; 450 } 451 452 and email_update = { 453 keywords : (keyword * bool) list option; 454 mailbox_ids : (id * bool) list option; 455 } 456 457 (** Email/set response. See RFC8621 Section 4.8. *) 458 type email_set_response = { 459 account_id : id; 460 old_state : string option; 461 new_state : string; 462 created : (id * email) list option; 463 updated : id list option; 464 destroyed : id list option; 465 not_created : (id * set_error) list option; 466 not_updated : (id * set_error) list option; 467 not_destroyed : (id * set_error) list option; 468 } 469 470 (** Email/copy request arguments. See RFC8621 Section 4.9. *) 471 type email_copy_arguments = { 472 from_account_id : id; 473 account_id : id; 474 create : (id * email_creation) list; 475 on_success_destroy_original : bool option; 476 } 477 478 (** Email/copy response. See RFC8621 Section 4.9. *) 479 type email_copy_response = { 480 from_account_id : id; 481 account_id : id; 482 created : (id * email) list option; 483 not_created : (id * set_error) list option; 484 } 485 486 (** Email/import request arguments. See RFC8621 Section 4.10. *) 487 type email_import_arguments = { 488 account_id : id; 489 emails : (id * email_import) list; 490 } 491 492 and email_import = { 493 blob_id : id; 494 mailbox_ids : (id * bool) list; 495 keywords : (keyword * bool) list option; 496 received_at : utc_date option; 497 } 498 499 (** Email/import response. See RFC8621 Section 4.10. *) 500 type email_import_response = { 501 account_id : id; 502 created : (id * email) list option; 503 not_created : (id * set_error) list option; 504 } 505 506 (** {1:search_snippet Search snippets} *) 507 508 (** SearchSnippet/get request arguments. See RFC8621 Section 4.11. *) 509 type search_snippet_get_arguments = { 510 account_id : id; 511 email_ids : id list; 512 filter : email_filter_condition; 513 } 514 515 (** SearchSnippet/get response. See RFC8621 Section 4.11. *) 516 type search_snippet_get_response = { 517 account_id : id; 518 list : (id * search_snippet) list; 519 not_found : id list; 520 } 521 522 and search_snippet = { 523 subject : string option; 524 preview : string option; 525 } 526 527 (** {1:submission EmailSubmission objects} *) 528 529 (** EmailSubmission address. See RFC8621 Section 5.1. *) 530 type submission_address = { 531 email : string; 532 parameters : (string * string) list option; 533 } 534 535 (** Email submission object. See RFC8621 Section 5.1. *) 536 type email_submission = { 537 id : id; 538 identity_id : id; 539 email_id : id; 540 thread_id : id; 541 envelope : envelope option; 542 send_at : utc_date option; 543 undo_status : [ 544 | `pending 545 | `final 546 | `canceled 547 ] option; 548 delivery_status : (string * submission_status) list option; 549 dsn_blob_ids : (string * id) list option; 550 mdn_blob_ids : (string * id) list option; 551 } 552 553 (** Envelope for mail submission. See RFC8621 Section 5.1. *) 554 and envelope = { 555 mail_from : submission_address; 556 rcpt_to : submission_address list; 557 } 558 559 (** Delivery status for submitted email. See RFC8621 Section 5.1. *) 560 and submission_status = { 561 smtp_reply : string; 562 delivered : string option; 563 } 564 565 (** EmailSubmission/get request arguments. See RFC8621 Section 5.3. *) 566 type email_submission_get_arguments = { 567 account_id : id; 568 ids : id list option; 569 properties : string list option; 570 } 571 572 (** EmailSubmission/get response. See RFC8621 Section 5.3. *) 573 type email_submission_get_response = { 574 account_id : id; 575 state : string; 576 list : email_submission list; 577 not_found : id list; 578 } 579 580 (** EmailSubmission/changes request arguments. See RFC8621 Section 5.4. *) 581 type email_submission_changes_arguments = { 582 account_id : id; 583 since_state : string; 584 max_changes : unsigned_int option; 585 } 586 587 (** EmailSubmission/changes response. See RFC8621 Section 5.4. *) 588 type email_submission_changes_response = { 589 account_id : id; 590 old_state : string; 591 new_state : string; 592 has_more_changes : bool; 593 created : id list; 594 updated : id list; 595 destroyed : id list; 596 } 597 598 (** EmailSubmission/query filter condition. See RFC8621 Section 5.5. *) 599 type email_submission_filter_condition = { 600 identity_id : id option; 601 email_id : id option; 602 thread_id : id option; 603 before : utc_date option; 604 after : utc_date option; 605 subject : string option; 606 } 607 608 type email_submission_query_filter = [ 609 | `And of email_submission_query_filter list 610 | `Or of email_submission_query_filter list 611 | `Not of email_submission_query_filter 612 | `Condition of email_submission_filter_condition 613 ] 614 615 (** EmailSubmission/query request arguments. See RFC8621 Section 5.5. *) 616 type email_submission_query_arguments = { 617 account_id : id; 618 filter : email_submission_query_filter option; 619 sort : comparator list option; 620 position : unsigned_int option; 621 anchor : id option; 622 anchor_offset : int_t option; 623 limit : unsigned_int option; 624 calculate_total : bool option; 625 } 626 627 (** EmailSubmission/query response. See RFC8621 Section 5.5. *) 628 type email_submission_query_response = { 629 account_id : id; 630 query_state : string; 631 can_calculate_changes : bool; 632 position : unsigned_int; 633 ids : id list; 634 total : unsigned_int option; 635 } 636 637 (** EmailSubmission/set request arguments. See RFC8621 Section 5.6. *) 638 type email_submission_set_arguments = { 639 account_id : id; 640 if_in_state : string option; 641 create : (id * email_submission_creation) list option; 642 update : (id * email_submission_update) list option; 643 destroy : id list option; 644 on_success_update_email : (id * email_update) list option; 645 } 646 647 and email_submission_creation = { 648 email_id : id; 649 identity_id : id; 650 envelope : envelope option; 651 send_at : utc_date option; 652 } 653 654 and email_submission_update = { 655 email_id : id option; 656 identity_id : id option; 657 envelope : envelope option; 658 undo_status : [`canceled] option; 659 } 660 661 (** EmailSubmission/set response. See RFC8621 Section 5.6. *) 662 type email_submission_set_response = { 663 account_id : id; 664 old_state : string option; 665 new_state : string; 666 created : (id * email_submission) list option; 667 updated : id list option; 668 destroyed : id list option; 669 not_created : (id * set_error) list option; 670 not_updated : (id * set_error) list option; 671 not_destroyed : (id * set_error) list option; 672 } 673 674 (** {1:identity Identity objects} *) 675 676 (** Identity for sending mail. See RFC8621 Section 6. *) 677 type identity = { 678 id : id; 679 name : string; 680 email : string; 681 reply_to : email_address list option; 682 bcc : email_address list option; 683 text_signature : string option; 684 html_signature : string option; 685 may_delete : bool; 686 } 687 688 (** Identity/get request arguments. See RFC8621 Section 6.1. *) 689 type identity_get_arguments = { 690 account_id : id; 691 ids : id list option; 692 properties : string list option; 693 } 694 695 (** Identity/get response. See RFC8621 Section 6.1. *) 696 type identity_get_response = { 697 account_id : id; 698 state : string; 699 list : identity list; 700 not_found : id list; 701 } 702 703 (** Identity/changes request arguments. See RFC8621 Section 6.2. *) 704 type identity_changes_arguments = { 705 account_id : id; 706 since_state : string; 707 max_changes : unsigned_int option; 708 } 709 710 (** Identity/changes response. See RFC8621 Section 6.2. *) 711 type identity_changes_response = { 712 account_id : id; 713 old_state : string; 714 new_state : string; 715 has_more_changes : bool; 716 created : id list; 717 updated : id list; 718 destroyed : id list; 719 } 720 721 (** Identity/set request arguments. See RFC8621 Section 6.3. *) 722 type identity_set_arguments = { 723 account_id : id; 724 if_in_state : string option; 725 create : (id * identity_creation) list option; 726 update : (id * identity_update) list option; 727 destroy : id list option; 728 } 729 730 and identity_creation = { 731 name : string; 732 email : string; 733 reply_to : email_address list option; 734 bcc : email_address list option; 735 text_signature : string option; 736 html_signature : string option; 737 } 738 739 and identity_update = { 740 name : string option; 741 email : string option; 742 reply_to : email_address list option; 743 bcc : email_address list option; 744 text_signature : string option; 745 html_signature : string option; 746 } 747 748 (** Identity/set response. See RFC8621 Section 6.3. *) 749 type identity_set_response = { 750 account_id : id; 751 old_state : string option; 752 new_state : string; 753 created : (id * identity) list option; 754 updated : id list option; 755 destroyed : id list option; 756 not_created : (id * set_error) list option; 757 not_updated : (id * set_error) list option; 758 not_destroyed : (id * set_error) list option; 759 } 760 761 (** {1:vacation_response VacationResponse objects} *) 762 763 (** Vacation auto-reply setting. See RFC8621 Section 7. *) 764 type vacation_response = { 765 id : id; 766 is_enabled : bool; 767 from_date : utc_date option; 768 to_date : utc_date option; 769 subject : string option; 770 text_body : string option; 771 html_body : string option; 772 } 773 774 (** VacationResponse/get request arguments. See RFC8621 Section 7.2. *) 775 type vacation_response_get_arguments = { 776 account_id : id; 777 ids : id list option; 778 properties : string list option; 779 } 780 781 (** VacationResponse/get response. See RFC8621 Section 7.2. *) 782 type vacation_response_get_response = { 783 account_id : id; 784 state : string; 785 list : vacation_response list; 786 not_found : id list; 787 } 788 789 (** VacationResponse/set request arguments. See RFC8621 Section 7.3. *) 790 type vacation_response_set_arguments = { 791 account_id : id; 792 if_in_state : string option; 793 update : (id * vacation_response_update) list; 794 } 795 796 and vacation_response_update = { 797 is_enabled : bool option; 798 from_date : utc_date option; 799 to_date : utc_date option; 800 subject : string option; 801 text_body : string option; 802 html_body : string option; 803 } 804 805 (** VacationResponse/set response. See RFC8621 Section 7.3. *) 806 type vacation_response_set_response = { 807 account_id : id; 808 old_state : string option; 809 new_state : string; 810 updated : id list option; 811 not_updated : (id * set_error) list option; 812 } 813end 814 815(** {1 JSON serialization} *) 816 817module Json = struct 818 open Types 819 820 (** {2 Helper functions for serialization} *) 821 822 let string_of_mailbox_role = function 823 | All -> "all" 824 | Archive -> "archive" 825 | Drafts -> "drafts" 826 | Flagged -> "flagged" 827 | Important -> "important" 828 | Inbox -> "inbox" 829 | Junk -> "junk" 830 | Sent -> "sent" 831 | Trash -> "trash" 832 | Unknown s -> s 833 834 let mailbox_role_of_string = function 835 | "all" -> All 836 | "archive" -> Archive 837 | "drafts" -> Drafts 838 | "flagged" -> Flagged 839 | "important" -> Important 840 | "inbox" -> Inbox 841 | "junk" -> Junk 842 | "sent" -> Sent 843 | "trash" -> Trash 844 | s -> Unknown s 845 846 let string_of_keyword = function 847 | Flagged -> "$flagged" 848 | Answered -> "$answered" 849 | Draft -> "$draft" 850 | Forwarded -> "$forwarded" 851 | Phishing -> "$phishing" 852 | Junk -> "$junk" 853 | NotJunk -> "$notjunk" 854 | Seen -> "$seen" 855 | Unread -> "$unread" 856 | Custom s -> s 857 858 let keyword_of_string = function 859 | "$flagged" -> Flagged 860 | "$answered" -> Answered 861 | "$draft" -> Draft 862 | "$forwarded" -> Forwarded 863 | "$phishing" -> Phishing 864 | "$junk" -> Junk 865 | "$notjunk" -> NotJunk 866 | "$seen" -> Seen 867 | "$unread" -> Unread 868 | s -> Custom s 869 870 (** {2 Mailbox serialization} *) 871 872 (** TODO:claude - Need to implement all JSON serialization functions 873 for each type we've defined. This would be a substantial amount of 874 code and likely require additional understanding of the ezjsonm API. 875 876 For a full implementation, we would need functions to convert between 877 OCaml types and JSON for each of: 878 - mailbox, mailbox_rights, mailbox query/update operations 879 - thread operations 880 - email, email_address, header, email_body_part 881 - email query/update operations 882 - submission operations 883 - identity operations 884 - vacation response operations 885 *) 886end 887 888(** {1 API functions} *) 889 890open Lwt.Syntax 891open Jmap.Api 892open Jmap.Types 893 894(** Authentication credentials for a JMAP server *) 895type credentials = { 896 username: string; 897 password: string; 898} 899 900(** Connection to a JMAP mail server *) 901type connection = { 902 session: Jmap.Types.session; 903 config: Jmap.Api.config; 904} 905 906(** Convert JSON mail object to OCaml type *) 907let mailbox_of_json json = 908 try 909 let open Ezjsonm in 910 Printf.printf "Parsing mailbox JSON\n"; 911 912 let id = get_string (find json ["id"]) in 913 Printf.printf "Got id: %s\n" id; 914 915 let name = get_string (find json ["name"]) in 916 Printf.printf "Got name: %s\n" name; 917 918 (* Handle parentId which can be null *) 919 let parent_id = 920 match find_opt json ["parentId"] with 921 | Some (`Null) -> None 922 | Some (`String s) -> Some s 923 | None -> None 924 | _ -> None 925 in 926 Printf.printf "Got parent_id: %s\n" (match parent_id with Some p -> p | None -> "None"); 927 928 (* Handle role which might be null *) 929 let role = 930 match find_opt json ["role"] with 931 | Some (`Null) -> None 932 | Some (`String s) -> Some (Json.mailbox_role_of_string s) 933 | None -> None 934 | _ -> None 935 in 936 Printf.printf "Got role\n"; 937 938 let sort_order = get_int (find json ["sortOrder"]) in 939 Printf.printf "Got sort_order: %d\n" sort_order; 940 941 let total_emails = get_int (find json ["totalEmails"]) in 942 Printf.printf "Got total_emails: %d\n" total_emails; 943 944 let unread_emails = get_int (find json ["unreadEmails"]) in 945 Printf.printf "Got unread_emails: %d\n" unread_emails; 946 947 let total_threads = get_int (find json ["totalThreads"]) in 948 Printf.printf "Got total_threads: %d\n" total_threads; 949 950 let unread_threads = get_int (find json ["unreadThreads"]) in 951 Printf.printf "Got unread_threads: %d\n" unread_threads; 952 953 let is_subscribed = get_bool (find json ["isSubscribed"]) in 954 Printf.printf "Got is_subscribed: %b\n" is_subscribed; 955 956 let rights_json = find json ["myRights"] in 957 Printf.printf "Got rights_json\n"; 958 959 let my_rights = { 960 Types.may_read_items = get_bool (find rights_json ["mayReadItems"]); 961 may_add_items = get_bool (find rights_json ["mayAddItems"]); 962 may_remove_items = get_bool (find rights_json ["mayRemoveItems"]); 963 may_set_seen = get_bool (find rights_json ["maySetSeen"]); 964 may_set_keywords = get_bool (find rights_json ["maySetKeywords"]); 965 may_create_child = get_bool (find rights_json ["mayCreateChild"]); 966 may_rename = get_bool (find rights_json ["mayRename"]); 967 may_delete = get_bool (find rights_json ["mayDelete"]); 968 may_submit = get_bool (find rights_json ["maySubmit"]); 969 } in 970 Printf.printf "Constructed my_rights\n"; 971 972 let result = { 973 Types.id; 974 name; 975 parent_id; 976 role; 977 sort_order; 978 total_emails; 979 unread_emails; 980 total_threads; 981 unread_threads; 982 is_subscribed; 983 my_rights; 984 } in 985 Printf.printf "Constructed mailbox result\n"; 986 987 Ok (result) 988 with 989 | Not_found as e -> 990 Printf.printf "Not_found error: %s\n" (Printexc.to_string e); 991 Printexc.print_backtrace stdout; 992 Error (Parse_error "Required field not found in mailbox object") 993 | Invalid_argument msg -> 994 Printf.printf "Invalid_argument error: %s\n" msg; 995 Error (Parse_error msg) 996 | e -> 997 Printf.printf "Unknown error: %s\n" (Printexc.to_string e); 998 Error (Parse_error (Printexc.to_string e)) 999 1000(** Convert JSON email object to OCaml type *) 1001let email_of_json json = 1002 try 1003 let open Ezjsonm in 1004 Printf.printf "Parsing email JSON\n"; 1005 1006 let id = get_string (find json ["id"]) in 1007 Printf.printf "Got email id: %s\n" id; 1008 1009 let blob_id = get_string (find json ["blobId"]) in 1010 let thread_id = get_string (find json ["threadId"]) in 1011 1012 (* Process mailboxIds map *) 1013 let mailbox_ids_json = find json ["mailboxIds"] in 1014 let mailbox_ids = match mailbox_ids_json with 1015 | `O items -> List.map (fun (id, v) -> (id, get_bool v)) items 1016 | _ -> raise (Invalid_argument "mailboxIds is not an object") 1017 in 1018 1019 (* Process keywords map *) 1020 let keywords_json = find json ["keywords"] in 1021 let keywords = match keywords_json with 1022 | `O items -> List.map (fun (k, v) -> 1023 (Json.keyword_of_string k, get_bool v)) items 1024 | _ -> raise (Invalid_argument "keywords is not an object") 1025 in 1026 1027 let size = get_int (find json ["size"]) in 1028 let received_at = get_string (find json ["receivedAt"]) in 1029 1030 (* Handle messageId which might be an array or missing *) 1031 let message_id = 1032 match find_opt json ["messageId"] with 1033 | Some (`A ids) -> List.map (fun id -> 1034 match id with 1035 | `String s -> s 1036 | _ -> raise (Invalid_argument "messageId item is not a string") 1037 ) ids 1038 | Some (`String s) -> [s] (* Handle single string case *) 1039 | None -> [] (* Handle missing case *) 1040 | _ -> raise (Invalid_argument "messageId has unexpected type") 1041 in 1042 1043 (* Parse optional fields *) 1044 let parse_email_addresses opt_json = 1045 match opt_json with 1046 | Some (`A items) -> 1047 Some (List.map (fun addr_json -> 1048 let name = 1049 match find_opt addr_json ["name"] with 1050 | Some (`String s) -> Some s 1051 | Some (`Null) -> None 1052 | None -> None 1053 | _ -> None 1054 in 1055 let email = get_string (find addr_json ["email"]) in 1056 let parameters = 1057 match find_opt addr_json ["parameters"] with 1058 | Some (`O items) -> List.map (fun (k, v) -> 1059 match v with 1060 | `String s -> (k, s) 1061 | _ -> (k, "") 1062 ) items 1063 | _ -> [] 1064 in 1065 { Types.name; email; parameters } 1066 ) items) 1067 | _ -> None 1068 in 1069 1070 (* Handle optional string arrays with null handling *) 1071 let parse_string_array_opt field_name = 1072 match find_opt json [field_name] with 1073 | Some (`A ids) -> 1074 Some (List.filter_map (function 1075 | `String s -> Some s 1076 | _ -> None 1077 ) ids) 1078 | Some (`Null) -> None 1079 | None -> None 1080 | _ -> None 1081 in 1082 1083 let in_reply_to = parse_string_array_opt "inReplyTo" in 1084 let references = parse_string_array_opt "references" in 1085 1086 let sender = parse_email_addresses (find_opt json ["sender"]) in 1087 let from = parse_email_addresses (find_opt json ["from"]) in 1088 let to_ = parse_email_addresses (find_opt json ["to"]) in 1089 let cc = parse_email_addresses (find_opt json ["cc"]) in 1090 let bcc = parse_email_addresses (find_opt json ["bcc"]) in 1091 let reply_to = parse_email_addresses (find_opt json ["replyTo"]) in 1092 1093 (* Handle optional string fields with null handling *) 1094 let parse_string_opt field_name = 1095 match find_opt json [field_name] with 1096 | Some (`String s) -> Some s 1097 | Some (`Null) -> None 1098 | None -> None 1099 | _ -> None 1100 in 1101 1102 let subject = parse_string_opt "subject" in 1103 let sent_at = parse_string_opt "sentAt" in 1104 1105 (* Handle optional boolean fields with null handling *) 1106 let parse_bool_opt field_name = 1107 match find_opt json [field_name] with 1108 | Some (`Bool b) -> Some b 1109 | Some (`Null) -> None 1110 | None -> None 1111 | _ -> None 1112 in 1113 1114 let has_attachment = parse_bool_opt "hasAttachment" in 1115 let preview = parse_string_opt "preview" in 1116 1117 (* Body parts parsing would go here - omitting for brevity *) 1118 Printf.printf "Email parsed successfully\n"; 1119 1120 Ok ({ 1121 Types.id; 1122 blob_id; 1123 thread_id; 1124 mailbox_ids; 1125 keywords; 1126 size; 1127 received_at; 1128 message_id; 1129 in_reply_to; 1130 references; 1131 sender; 1132 from; 1133 to_; 1134 cc; 1135 bcc; 1136 reply_to; 1137 subject; 1138 sent_at; 1139 has_attachment; 1140 preview; 1141 body_values = None; 1142 text_body = None; 1143 html_body = None; 1144 attachments = None; 1145 headers = None; 1146 }) 1147 with 1148 | Not_found as e -> 1149 Printf.printf "Email parse error - Not_found: %s\n" (Printexc.to_string e); 1150 Printexc.print_backtrace stdout; 1151 Error (Parse_error "Required field not found in email object") 1152 | Invalid_argument msg -> 1153 Printf.printf "Email parse error - Invalid_argument: %s\n" msg; 1154 Error (Parse_error msg) 1155 | e -> 1156 Printf.printf "Email parse error - Unknown: %s\n" (Printexc.to_string e); 1157 Error (Parse_error (Printexc.to_string e)) 1158 1159(** Login to a JMAP server and establish a connection 1160 @param uri The URI of the JMAP server 1161 @param credentials Authentication credentials 1162 @return A connection object if successful 1163 1164 TODO:claude *) 1165let login ~uri ~credentials = 1166 let* session_result = get_session (Uri.of_string uri) 1167 ~username:credentials.username 1168 ~authentication_token:credentials.password 1169 () in 1170 match session_result with 1171 | Ok session -> 1172 let api_uri = Uri.of_string session.api_url in 1173 let config = { 1174 api_uri; 1175 username = credentials.username; 1176 authentication_token = credentials.password; 1177 } in 1178 Lwt.return (Ok { session; config }) 1179 | Error e -> Lwt.return (Error e) 1180 1181(** Login to a JMAP server using an API token 1182 @param uri The URI of the JMAP server 1183 @param api_token The API token for authentication 1184 @return A connection object if successful 1185 1186 TODO:claude *) 1187let login_with_token ~uri ~api_token = 1188 let* session_result = get_session (Uri.of_string uri) 1189 ~api_token 1190 () in 1191 match session_result with 1192 | Ok session -> 1193 let api_uri = Uri.of_string session.api_url in 1194 let config = { 1195 api_uri; 1196 username = ""; (* Empty username indicates we're using token auth *) 1197 authentication_token = api_token; 1198 } in 1199 Lwt.return (Ok { session; config }) 1200 | Error e -> Lwt.return (Error e) 1201 1202(** Get all mailboxes for an account 1203 @param conn The JMAP connection 1204 @param account_id The account ID to get mailboxes for 1205 @return A list of mailboxes if successful 1206 1207 TODO:claude *) 1208let get_mailboxes conn ~account_id = 1209 let request = { 1210 using = ["urn:ietf:params:jmap:core"; Types.capability_mail]; 1211 method_calls = [ 1212 { 1213 name = "Mailbox/get"; 1214 arguments = `O [ 1215 ("accountId", `String account_id); 1216 ]; 1217 method_call_id = "m1"; 1218 } 1219 ]; 1220 created_ids = None; 1221 } in 1222 1223 let* response_result = make_request conn.config request in 1224 match response_result with 1225 | Ok response -> 1226 let result = 1227 try 1228 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1229 inv.name = "Mailbox/get") response.method_responses in 1230 let args = method_response.arguments in 1231 match Ezjsonm.find_opt args ["list"] with 1232 | Some (`A mailbox_list) -> 1233 let parse_results = List.map mailbox_of_json mailbox_list in 1234 let (successes, failures) = List.partition Result.is_ok parse_results in 1235 if List.length failures > 0 then 1236 Error (Parse_error "Failed to parse some mailboxes") 1237 else 1238 Ok (List.map Result.get_ok successes) 1239 | _ -> Error (Parse_error "Mailbox list not found in response") 1240 with 1241 | Not_found -> Error (Parse_error "Mailbox/get method response not found") 1242 | e -> Error (Parse_error (Printexc.to_string e)) 1243 in 1244 Lwt.return result 1245 | Error e -> Lwt.return (Error e) 1246 1247(** Get a specific mailbox by ID 1248 @param conn The JMAP connection 1249 @param account_id The account ID 1250 @param mailbox_id The mailbox ID to retrieve 1251 @return The mailbox if found 1252 1253 TODO:claude *) 1254let get_mailbox conn ~account_id ~mailbox_id = 1255 let request = { 1256 using = ["urn:ietf:params:jmap:core"; Types.capability_mail]; 1257 method_calls = [ 1258 { 1259 name = "Mailbox/get"; 1260 arguments = `O [ 1261 ("accountId", `String account_id); 1262 ("ids", `A [`String mailbox_id]); 1263 ]; 1264 method_call_id = "m1"; 1265 } 1266 ]; 1267 created_ids = None; 1268 } in 1269 1270 let* response_result = make_request conn.config request in 1271 match response_result with 1272 | Ok response -> 1273 let result = 1274 try 1275 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1276 inv.name = "Mailbox/get") response.method_responses in 1277 let args = method_response.arguments in 1278 match Ezjsonm.find_opt args ["list"] with 1279 | Some (`A [mailbox]) -> mailbox_of_json mailbox 1280 | Some (`A []) -> Error (Parse_error ("Mailbox not found: " ^ mailbox_id)) 1281 | _ -> Error (Parse_error "Expected single mailbox in response") 1282 with 1283 | Not_found -> Error (Parse_error "Mailbox/get method response not found") 1284 | e -> Error (Parse_error (Printexc.to_string e)) 1285 in 1286 Lwt.return result 1287 | Error e -> Lwt.return (Error e) 1288 1289(** Get messages in a mailbox 1290 @param conn The JMAP connection 1291 @param account_id The account ID 1292 @param mailbox_id The mailbox ID to get messages from 1293 @param limit Optional limit on number of messages to return 1294 @return The list of email messages if successful 1295 1296 TODO:claude *) 1297let get_messages_in_mailbox conn ~account_id ~mailbox_id ?limit () = 1298 (* First query the emails in the mailbox *) 1299 let query_request = { 1300 using = ["urn:ietf:params:jmap:core"; Types.capability_mail]; 1301 method_calls = [ 1302 { 1303 name = "Email/query"; 1304 arguments = `O ([ 1305 ("accountId", `String account_id); 1306 ("filter", `O [("inMailbox", `String mailbox_id)]); 1307 ("sort", `A [`O [("property", `String "receivedAt"); ("isAscending", `Bool false)]]); 1308 ] @ (match limit with 1309 | Some l -> [("limit", `Float (float_of_int l))] 1310 | None -> [] 1311 )); 1312 method_call_id = "q1"; 1313 } 1314 ]; 1315 created_ids = None; 1316 } in 1317 1318 let* query_result = make_request conn.config query_request in 1319 match query_result with 1320 | Ok query_response -> 1321 (try 1322 let query_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1323 inv.name = "Email/query") query_response.method_responses in 1324 let args = query_method.arguments in 1325 match Ezjsonm.find_opt args ["ids"] with 1326 | Some (`A ids) -> 1327 let email_ids = List.map (function 1328 | `String id -> id 1329 | _ -> raise (Invalid_argument "Email ID is not a string") 1330 ) ids in 1331 1332 (* If we have IDs, fetch the actual email objects *) 1333 if List.length email_ids > 0 then 1334 let get_request = { 1335 using = ["urn:ietf:params:jmap:core"; Types.capability_mail]; 1336 method_calls = [ 1337 { 1338 name = "Email/get"; 1339 arguments = `O [ 1340 ("accountId", `String account_id); 1341 ("ids", `A (List.map (fun id -> `String id) email_ids)); 1342 ]; 1343 method_call_id = "g1"; 1344 } 1345 ]; 1346 created_ids = None; 1347 } in 1348 1349 let* get_result = make_request conn.config get_request in 1350 match get_result with 1351 | Ok get_response -> 1352 (try 1353 let get_method = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1354 inv.name = "Email/get") get_response.method_responses in 1355 let args = get_method.arguments in 1356 match Ezjsonm.find_opt args ["list"] with 1357 | Some (`A email_list) -> 1358 let parse_results = List.map email_of_json email_list in 1359 let (successes, failures) = List.partition Result.is_ok parse_results in 1360 if List.length failures > 0 then 1361 Lwt.return (Error (Parse_error "Failed to parse some emails")) 1362 else 1363 Lwt.return (Ok (List.map Result.get_ok successes)) 1364 | _ -> Lwt.return (Error (Parse_error "Email list not found in response")) 1365 with 1366 | Not_found -> Lwt.return (Error (Parse_error "Email/get method response not found")) 1367 | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1368 | Error e -> Lwt.return (Error e) 1369 else 1370 (* No emails in mailbox *) 1371 Lwt.return (Ok []) 1372 1373 | _ -> Lwt.return (Error (Parse_error "Email IDs not found in query response")) 1374 with 1375 | Not_found -> Lwt.return (Error (Parse_error "Email/query method response not found")) 1376 | Invalid_argument msg -> Lwt.return (Error (Parse_error msg)) 1377 | e -> Lwt.return (Error (Parse_error (Printexc.to_string e)))) 1378 | Error e -> Lwt.return (Error e) 1379 1380(** Get a single email message by ID 1381 @param conn The JMAP connection 1382 @param account_id The account ID 1383 @param email_id The email ID to retrieve 1384 @return The email message if found 1385 1386 TODO:claude *) 1387let get_email conn ~account_id ~email_id = 1388 let request = { 1389 using = ["urn:ietf:params:jmap:core"; Types.capability_mail]; 1390 method_calls = [ 1391 { 1392 name = "Email/get"; 1393 arguments = `O [ 1394 ("accountId", `String account_id); 1395 ("ids", `A [`String email_id]); 1396 ]; 1397 method_call_id = "m1"; 1398 } 1399 ]; 1400 created_ids = None; 1401 } in 1402 1403 let* response_result = make_request conn.config request in 1404 match response_result with 1405 | Ok response -> 1406 let result = 1407 try 1408 let method_response = List.find (fun (inv : Ezjsonm.value Jmap.Types.invocation) -> 1409 inv.name = "Email/get") response.method_responses in 1410 let args = method_response.arguments in 1411 match Ezjsonm.find_opt args ["list"] with 1412 | Some (`A [email]) -> email_of_json email 1413 | Some (`A []) -> Error (Parse_error ("Email not found: " ^ email_id)) 1414 | _ -> Error (Parse_error "Expected single email in response") 1415 with 1416 | Not_found -> Error (Parse_error "Email/get method response not found") 1417 | e -> Error (Parse_error (Printexc.to_string e)) 1418 in 1419 Lwt.return result 1420 | Error e -> Lwt.return (Error e)