this repo has no description
at main 11 kB view raw
1(** 2 * fastmail_list - Lists emails from a Fastmail account using JMAP API 3 * 4 * This binary connects to the Fastmail JMAP API using an authentication token 5 * from the JMAP_API_TOKEN environment variable and lists the most recent 100 6 * emails with their subjects, sender details, and labels. 7 * 8 * Usage: 9 * JMAP_API_TOKEN=your_api_token ./fastmail_list [options] 10 * 11 * Options: 12 * --unread List only unread messages 13 * --labels Show labels/keywords associated with messages 14 * --debug=LEVEL Set debug level (0-4, where 4 is most verbose) 15 * --from=PATTERN Filter messages by sender email address 16 * --demo-refs Demonstrate result references feature 17 *) 18 19open Lwt.Syntax 20open Jmap 21open Jmap_mail 22open Cmdliner 23module Mail = Jmap_mail.Types 24 25(** Prints the email details *) 26let print_email ~show_labels (email : Mail.email) = 27 let sender = 28 match email.from with 29 | Some (addr :: _) -> 30 (match addr.name with 31 | Some name -> Printf.sprintf "%s <%s>" name addr.email 32 | None -> addr.email) 33 | _ -> "<unknown>" 34 in 35 let subject = 36 match email.subject with 37 | Some s -> s 38 | None -> "<no subject>" 39 in 40 let date = email.received_at in 41 42 (* Format labels/keywords if requested *) 43 let labels_str = 44 if show_labels then 45 let formatted = Jmap_mail.Types.format_email_keywords email.keywords in 46 if formatted <> "" then 47 " [" ^ formatted ^ "]" 48 else 49 "" 50 else 51 "" 52 in 53 54 Printf.printf "%s | %s | %s%s\n" date sender subject labels_str 55 56(** Check if an email is unread *) 57let is_unread (email : Mail.email) = 58 let is_unread_keyword = 59 List.exists (fun (kw, active) -> 60 kw = Mail.Unread && active 61 ) email.keywords 62 in 63 let is_not_seen = 64 not (List.exists (fun (kw, active) -> 65 kw = Mail.Seen && active 66 ) email.keywords) 67 in 68 is_unread_keyword || is_not_seen 69 70(** Example function demonstrating how to use higher-level library functions for JMAP requests *) 71let demo_result_references conn account_id = 72 Printf.printf "\nResult Reference Demo:\n"; 73 Printf.printf "=====================\n"; 74 75 (* Step 1: Get all mailboxes *) 76 let* mailboxes_result = Jmap_mail.get_mailboxes conn ~account_id in 77 match mailboxes_result with 78 | Error err -> 79 Printf.printf "Error getting mailboxes: %s\n" (Api.string_of_error err); 80 Lwt.return_unit 81 82 | Ok mailboxes -> 83 (* Step 2: Get the first mailbox for this demonstration *) 84 match mailboxes with 85 | [] -> 86 Printf.printf "No mailboxes found.\n"; 87 Lwt.return_unit 88 89 | first_mailbox :: _ -> 90 Printf.printf "Using mailbox: %s\n" first_mailbox.Mail.name; 91 92 (* Step 3: Get emails from the selected mailbox *) 93 let* emails_result = Jmap_mail.get_messages_in_mailbox 94 conn 95 ~account_id 96 ~mailbox_id:first_mailbox.Mail.id 97 ~limit:10 98 () 99 in 100 101 match emails_result with 102 | Error err -> 103 Printf.printf "Error getting emails: %s\n" (Api.string_of_error err); 104 Lwt.return_unit 105 106 | Ok emails -> 107 Printf.printf "Successfully retrieved %d emails using the high-level library API!\n" 108 (List.length emails); 109 110 (* Display some basic information about the emails *) 111 List.iteri (fun i (email:Jmap_mail.Types.email) -> 112 let subject = Option.value ~default:"<no subject>" email.Mail.subject in 113 Printf.printf " %d. %s\n" (i + 1) subject 114 ) emails; 115 116 Lwt.return_unit 117 118(** Main function for listing emails *) 119let list_emails unread_only show_labels debug_level demo_refs sender_filter = 120 (* Configure logging *) 121 init_logging ~level:debug_level ~enable_logs:(debug_level > 0) ~redact_sensitive:true (); 122 123 match Sys.getenv_opt "JMAP_API_TOKEN" with 124 | None -> 125 Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n"; 126 Printf.eprintf "Usage: JMAP_API_TOKEN=your_token fastmail-list [options]\n"; 127 exit 1 128 | Some token -> 129 (* Only print token info at Info level or higher *) 130 Logs.info (fun m -> m "Using API token: %s" (redact_token token)); 131 132 (* Connect to Fastmail JMAP API *) 133 let formatted_token = token in 134 135 (* Only print instructions at Info level *) 136 let level = match Logs.level () with 137 | None -> 0 138 | Some Logs.Error -> 1 139 | Some Logs.Info -> 2 140 | Some Logs.Debug -> 3 141 | _ -> 2 142 in 143 if level >= 2 then begin 144 Printf.printf "\nFastmail API Instructions:\n"; 145 Printf.printf "1. Get a token from: https://app.fastmail.com/settings/tokens\n"; 146 Printf.printf "2. Create a new token with Mail scope (read/write)\n"; 147 Printf.printf "3. Copy the full token (example: 3de40-5fg1h2-a1b2c3...)\n"; 148 Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" fastmail-list [options]\n\n"; 149 Printf.printf "Note: This example is working correctly but needs a valid Fastmail token.\n\n"; 150 end; 151 let* result = login_with_token 152 ~uri:"https://api.fastmail.com/jmap/session" 153 ~api_token:formatted_token 154 in 155 match result with 156 | Error err -> 157 Printf.eprintf "%s\n" (Api.string_of_error err); 158 Lwt.return 1 159 | Ok conn -> 160 (* Get the primary account ID *) 161 let primary_account_id = 162 let mail_capability = Jmap_mail.Capability.to_string Jmap_mail.Capability.Mail in 163 match List.assoc_opt mail_capability conn.session.primary_accounts with 164 | Some id -> id 165 | None -> 166 match conn.session.accounts with 167 | (id, _) :: _ -> id 168 | [] -> 169 Printf.eprintf "No accounts found\n"; 170 exit 1 171 in 172 173 (* Run result references demo if requested *) 174 let* () = 175 if demo_refs then 176 demo_result_references conn primary_account_id 177 else 178 Lwt.return_unit 179 in 180 181 (* Get the Inbox mailbox *) 182 let* mailboxes_result = get_mailboxes conn ~account_id:primary_account_id in 183 match mailboxes_result with 184 | Error err -> 185 Printf.eprintf "Failed to get mailboxes: %s\n" (Api.string_of_error err); 186 Lwt.return 1 187 | Ok mailboxes -> 188 (* If there's a mailbox list, just use the first one for this example *) 189 let inbox_id = 190 match mailboxes with 191 | mailbox :: _ -> mailbox.Mail.id 192 | [] -> 193 Printf.eprintf "No mailboxes found\n"; 194 exit 1 195 in 196 197 (* Get messages from inbox *) 198 let* emails_result = get_messages_in_mailbox 199 conn 200 ~account_id:primary_account_id 201 ~mailbox_id:inbox_id 202 ~limit:1000 203 () 204 in 205 match emails_result with 206 | Error err -> 207 Printf.eprintf "Failed to get emails: %s\n" (Api.string_of_error err); 208 Lwt.return 1 209 | Ok emails -> 210 (* Apply filters based on command line arguments *) 211 let filtered_by_unread = 212 if unread_only then 213 List.filter is_unread emails 214 else 215 emails 216 in 217 218 (* Apply sender filter if specified *) 219 let filtered_emails = 220 if sender_filter <> "" then begin 221 Printf.printf "Filtering by sender: %s\n" sender_filter; 222 List.filter (fun email -> 223 Jmap_mail.email_matches_sender email sender_filter 224 ) filtered_by_unread 225 end else 226 filtered_by_unread 227 in 228 229 (* Create description of applied filters *) 230 let filter_description = 231 let parts = [] in 232 let parts = if unread_only then "unread" :: parts else parts in 233 let parts = if sender_filter <> "" then ("from \"" ^ sender_filter ^ "\"") :: parts else parts in 234 match parts with 235 | [] -> "the most recent" 236 | [p] -> p 237 | _ -> String.concat " and " parts 238 in 239 240 Printf.printf "Listing %s %d emails in your inbox:\n" 241 filter_description 242 (List.length filtered_emails); 243 Printf.printf "--------------------------------------------\n"; 244 List.iter (print_email ~show_labels) filtered_emails; 245 Lwt.return 0 246 247(** Command line interface *) 248let unread_only = 249 let doc = "List only unread messages" in 250 Arg.(value & flag & info ["unread"] ~doc) 251 252let show_labels = 253 let doc = "Show labels/keywords associated with messages" in 254 Arg.(value & flag & info ["labels"] ~doc) 255 256let debug_level = 257 let doc = "Set debug level (0-4, where 4 is most verbose)" in 258 Arg.(value & opt int 0 & info ["debug"] ~docv:"LEVEL" ~doc) 259 260let demo_refs = 261 let doc = "Demonstrate result references feature" in 262 Arg.(value & flag & info ["demo-refs"] ~doc) 263 264let sender_filter = 265 let doc = "Filter messages by sender email address (supports wildcards: * and ?)" in 266 Arg.(value & opt string "" & info ["from"] ~docv:"PATTERN" ~doc) 267 268let cmd = 269 let doc = "List emails from a Fastmail account using JMAP API" in 270 let man = [ 271 `S Manpage.s_description; 272 `P "This program connects to the Fastmail JMAP API using an authentication token 273 from the JMAP_API_TOKEN environment variable and lists the most recent emails 274 with their subjects, sender details, and labels."; 275 `P "You must obtain a Fastmail API token from https://app.fastmail.com/settings/tokens 276 and set it in the JMAP_API_TOKEN environment variable."; 277 `S Manpage.s_environment; 278 `P "$(b,JMAP_API_TOKEN) The Fastmail API authentication token (required)"; 279 `S Manpage.s_examples; 280 `P "List all emails:"; 281 `P " $(mname) $(i,JMAP_API_TOKEN=your_token)"; 282 `P "List only unread emails:"; 283 `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread"; 284 `P "List emails from a specific sender:"; 285 `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --from=user@example.com"; 286 `P "List unread emails with labels:"; 287 `P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread --labels"; 288 ] in 289 let info = Cmd.info "fastmail-list" ~doc ~man in 290 Cmd.v info Term.(const (fun u l d r s -> 291 Lwt_main.run (list_emails u l d r s) 292 ) $ unread_only $ show_labels $ debug_level $ demo_refs $ sender_filter) 293 294(** Program entry point *) 295let () = exit (Cmd.eval_value cmd |> function 296 | Ok (`Ok exit_code) -> exit_code 297 | Ok (`Version | `Help) -> 0 298 | Error _ -> 1)