validops-east-1 commited on
Commit
715cb56
·
1 Parent(s): 55ecbeb

feat: improvements

Browse files
app/api/v1/gmail.py CHANGED
@@ -1,7 +1,7 @@
1
  from __future__ import annotations
2
 
3
  import time
4
- from typing import Any, Dict, List, Optional
5
 
6
  from fastapi import APIRouter, Depends, Header, HTTPException
7
 
@@ -17,13 +17,16 @@ from app.models.schemas import (
17
  GmailDraftUpdateRequest,
18
  GmailFilterCreateRequest,
19
  GmailGenericResponse,
 
20
  GmailInsertMessageRequest,
 
21
  GmailLabelRequest,
22
  GmailMessageModifyRequest,
23
  GmailRawMessageRequest,
24
  GmailRefreshResponse,
25
  GmailSendResponse,
26
  GmailSettingsUpdateRequest,
 
27
  )
28
  from app.services.gmail_service import (
29
  GmailAPIError,
@@ -177,6 +180,77 @@ async def get_profile(
177
  return _ok(start, creds, data)
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  # ---------------------------------------------------------------------------
181
  # Messages
182
  # ---------------------------------------------------------------------------
@@ -362,6 +436,37 @@ async def insert_message(
362
  )
363
 
364
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  @router.post("/messages/batchModify", response_model=GmailGenericResponse,
366
  summary="Add/remove labels across many messages")
367
  async def batch_modify(
@@ -802,6 +907,30 @@ async def update_label(
802
  return _ok(start, creds, data)
803
 
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  @router.delete("/labels/{label_id}", response_model=GmailGenericResponse,
806
  summary="Delete a user label")
807
  async def delete_label(
@@ -889,6 +1018,105 @@ async def update_vacation(
889
  return _ok(start, creds, data)
890
 
891
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  @router.get("/settings/filters", response_model=GmailGenericResponse,
893
  summary="List all filters")
894
  async def list_filters(
@@ -1111,6 +1339,23 @@ async def delete_send_as(
1111
  return _ok(start, creds, {"deleted": send_as_email})
1112
 
1113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1114
  @router.get("/settings/delegates", response_model=GmailGenericResponse,
1115
  summary="List delegates")
1116
  async def list_delegates(
 
1
  from __future__ import annotations
2
 
3
  import time
4
+ from typing import Any, Dict, Optional
5
 
6
  from fastapi import APIRouter, Depends, Header, HTTPException
7
 
 
17
  GmailDraftUpdateRequest,
18
  GmailFilterCreateRequest,
19
  GmailGenericResponse,
20
+ GmailImportMessageRequest,
21
  GmailInsertMessageRequest,
22
+ GmailLabelPatchRequest,
23
  GmailLabelRequest,
24
  GmailMessageModifyRequest,
25
  GmailRawMessageRequest,
26
  GmailRefreshResponse,
27
  GmailSendResponse,
28
  GmailSettingsUpdateRequest,
29
+ GmailWatchRequest,
30
  )
31
  from app.services.gmail_service import (
32
  GmailAPIError,
 
180
  return _ok(start, creds, data)
181
 
182
 
183
+ # ---------------------------------------------------------------------------
184
+ # Watch / Stop / History (push notifications & change history)
185
+ # ---------------------------------------------------------------------------
186
+
187
+ @router.post("/watch", response_model=GmailGenericResponse,
188
+ summary="Set up push notifications for mailbox changes (users.watch)")
189
+ async def watch_mailbox(
190
+ body: GmailWatchRequest,
191
+ creds: GmailCredentials = Depends(_credentials),
192
+ service: GmailService = Depends(get_gmail_service),
193
+ ):
194
+ start = time.perf_counter()
195
+ try:
196
+ data = await service.watch(
197
+ creds,
198
+ user_id=body.user_id,
199
+ topic_name=body.topic_name,
200
+ label_ids=body.label_ids,
201
+ label_filter_action=body.label_filter_action,
202
+ )
203
+ except GmailAPIError as exc:
204
+ raise _http_error(exc) from exc
205
+ _logger.info("Gmail watch enabled for %s (%.2fms)", body.user_id, _elapsed_ms(start))
206
+ return _ok(start, creds, data)
207
+
208
+
209
+ @router.post("/stop", response_model=GmailGenericResponse,
210
+ summary="Stop receiving push notifications (users.stop)")
211
+ async def stop_watch_mailbox(
212
+ creds: GmailCredentials = Depends(_credentials),
213
+ service: GmailService = Depends(get_gmail_service),
214
+ user_id: Optional[str] = None,
215
+ ):
216
+ start = time.perf_counter()
217
+ try:
218
+ await service.stop_watch(creds, user_id=user_id or "me")
219
+ except GmailAPIError as exc:
220
+ raise _http_error(exc) from exc
221
+ _logger.info("Gmail watch stopped (%.2fms)", _elapsed_ms(start))
222
+ return _ok(start, creds, {"stopped": True})
223
+
224
+
225
+ @router.get("/history", response_model=GmailGenericResponse,
226
+ summary="List mailbox change history (users.history.list)")
227
+ async def list_history(
228
+ creds: GmailCredentials = Depends(_credentials),
229
+ service: GmailService = Depends(get_gmail_service),
230
+ start_history_id: int = ...,
231
+ label_id: Optional[str] = None,
232
+ history_types: Optional[str] = None,
233
+ max_results: Optional[int] = None,
234
+ page_token: Optional[str] = None,
235
+ user_id: Optional[str] = None,
236
+ ):
237
+ start = time.perf_counter()
238
+ try:
239
+ data = await service.list_history(
240
+ creds,
241
+ user_id=user_id or "me",
242
+ start_history_id=start_history_id,
243
+ label_id=label_id,
244
+ history_types=history_types.split(",") if history_types else None,
245
+ max_results=max_results,
246
+ page_token=page_token,
247
+ )
248
+ except GmailAPIError as exc:
249
+ raise _http_error(exc) from exc
250
+ _logger.info("Gmail history listed (%.2fms)", _elapsed_ms(start))
251
+ return _ok(start, creds, data)
252
+
253
+
254
  # ---------------------------------------------------------------------------
255
  # Messages
256
  # ---------------------------------------------------------------------------
 
436
  )
437
 
438
 
439
+ @router.post("/messages/import", response_model=GmailSendResponse,
440
+ summary="Import an RFC 2822 message into the mailbox (users.messages.import)")
441
+ async def import_message(
442
+ body: GmailImportMessageRequest,
443
+ creds: GmailCredentials = Depends(_credentials),
444
+ service: GmailService = Depends(get_gmail_service),
445
+ ):
446
+ start = time.perf_counter()
447
+ try:
448
+ data = await service.import_message(
449
+ creds,
450
+ body.raw,
451
+ user_id=_user_id(body.delegate),
452
+ label_ids=body.label_ids,
453
+ internal_date_source=body.internal_date_source,
454
+ never_mark_spam=body.never_mark_spam,
455
+ process_for_calendar=body.process_for_calendar,
456
+ )
457
+ except GmailAPIError as exc:
458
+ raise _http_error(exc) from exc
459
+ _logger.info("Gmail message imported (%.2fms)", _elapsed_ms(start))
460
+ return GmailSendResponse(
461
+ success=True,
462
+ time_ms=_elapsed_ms(start),
463
+ message_id=data.get("id"),
464
+ thread_id=data.get("threadId"),
465
+ refreshed_access_token=creds.refreshed_access_token,
466
+ data=data,
467
+ )
468
+
469
+
470
  @router.post("/messages/batchModify", response_model=GmailGenericResponse,
471
  summary="Add/remove labels across many messages")
472
  async def batch_modify(
 
907
  return _ok(start, creds, data)
908
 
909
 
910
+ @router.patch("/labels/{label_id}", response_model=GmailGenericResponse,
911
+ summary="Partially update a user label (users.labels.patch)")
912
+ async def patch_label(
913
+ label_id: str,
914
+ body: GmailLabelPatchRequest,
915
+ creds: GmailCredentials = Depends(_credentials),
916
+ service: GmailService = Depends(get_gmail_service),
917
+ user_id: Optional[str] = None,
918
+ ):
919
+ start = time.perf_counter()
920
+ payload = body.model_dump(exclude_none=True)
921
+ if not payload:
922
+ raise HTTPException(
923
+ status_code=400,
924
+ detail="At least one label field (name, label_list_visibility, message_list_visibility, color) is required.",
925
+ )
926
+ try:
927
+ data = await service.patch_label(creds, label_id, payload, user_id=user_id or "me")
928
+ except GmailAPIError as exc:
929
+ raise _http_error(exc) from exc
930
+ _logger.info("Gmail label %s patched (%.2fms)", label_id, _elapsed_ms(start))
931
+ return _ok(start, creds, data)
932
+
933
+
934
  @router.delete("/labels/{label_id}", response_model=GmailGenericResponse,
935
  summary="Delete a user label")
936
  async def delete_label(
 
1018
  return _ok(start, creds, data)
1019
 
1020
 
1021
+ @router.get("/settings/imap", response_model=GmailGenericResponse,
1022
+ summary="Get IMAP settings")
1023
+ async def get_imap(
1024
+ creds: GmailCredentials = Depends(_credentials),
1025
+ service: GmailService = Depends(get_gmail_service),
1026
+ user_id: Optional[str] = None,
1027
+ ):
1028
+ start = time.perf_counter()
1029
+ try:
1030
+ data = await service.get_imap(creds, user_id=user_id or "me")
1031
+ except GmailAPIError as exc:
1032
+ raise _http_error(exc) from exc
1033
+ _logger.info("Gmail IMAP settings fetched (%.2fms)", _elapsed_ms(start))
1034
+ return _ok(start, creds, data)
1035
+
1036
+
1037
+ @router.put("/settings/imap", response_model=GmailGenericResponse,
1038
+ summary="Update IMAP settings")
1039
+ async def update_imap(
1040
+ body: GmailSettingsUpdateRequest,
1041
+ creds: GmailCredentials = Depends(_credentials),
1042
+ service: GmailService = Depends(get_gmail_service),
1043
+ user_id: Optional[str] = None,
1044
+ ):
1045
+ start = time.perf_counter()
1046
+ try:
1047
+ data = await service.update_imap(creds, body.payload, user_id=user_id or "me")
1048
+ except GmailAPIError as exc:
1049
+ raise _http_error(exc) from exc
1050
+ _logger.info("Gmail IMAP settings updated (%.2fms)", _elapsed_ms(start))
1051
+ return _ok(start, creds, data)
1052
+
1053
+
1054
+ @router.get("/settings/pop", response_model=GmailGenericResponse,
1055
+ summary="Get POP settings")
1056
+ async def get_pop(
1057
+ creds: GmailCredentials = Depends(_credentials),
1058
+ service: GmailService = Depends(get_gmail_service),
1059
+ user_id: Optional[str] = None,
1060
+ ):
1061
+ start = time.perf_counter()
1062
+ try:
1063
+ data = await service.get_pop(creds, user_id=user_id or "me")
1064
+ except GmailAPIError as exc:
1065
+ raise _http_error(exc) from exc
1066
+ _logger.info("Gmail POP settings fetched (%.2fms)", _elapsed_ms(start))
1067
+ return _ok(start, creds, data)
1068
+
1069
+
1070
+ @router.put("/settings/pop", response_model=GmailGenericResponse,
1071
+ summary="Update POP settings")
1072
+ async def update_pop(
1073
+ body: GmailSettingsUpdateRequest,
1074
+ creds: GmailCredentials = Depends(_credentials),
1075
+ service: GmailService = Depends(get_gmail_service),
1076
+ user_id: Optional[str] = None,
1077
+ ):
1078
+ start = time.perf_counter()
1079
+ try:
1080
+ data = await service.update_pop(creds, body.payload, user_id=user_id or "me")
1081
+ except GmailAPIError as exc:
1082
+ raise _http_error(exc) from exc
1083
+ _logger.info("Gmail POP settings updated (%.2fms)", _elapsed_ms(start))
1084
+ return _ok(start, creds, data)
1085
+
1086
+
1087
+ @router.get("/settings/language", response_model=GmailGenericResponse,
1088
+ summary="Get language settings")
1089
+ async def get_language(
1090
+ creds: GmailCredentials = Depends(_credentials),
1091
+ service: GmailService = Depends(get_gmail_service),
1092
+ user_id: Optional[str] = None,
1093
+ ):
1094
+ start = time.perf_counter()
1095
+ try:
1096
+ data = await service.get_language(creds, user_id=user_id or "me")
1097
+ except GmailAPIError as exc:
1098
+ raise _http_error(exc) from exc
1099
+ _logger.info("Gmail language settings fetched (%.2fms)", _elapsed_ms(start))
1100
+ return _ok(start, creds, data)
1101
+
1102
+
1103
+ @router.put("/settings/language", response_model=GmailGenericResponse,
1104
+ summary="Update language settings")
1105
+ async def update_language(
1106
+ body: GmailSettingsUpdateRequest,
1107
+ creds: GmailCredentials = Depends(_credentials),
1108
+ service: GmailService = Depends(get_gmail_service),
1109
+ user_id: Optional[str] = None,
1110
+ ):
1111
+ start = time.perf_counter()
1112
+ try:
1113
+ data = await service.update_language(creds, body.payload, user_id=user_id or "me")
1114
+ except GmailAPIError as exc:
1115
+ raise _http_error(exc) from exc
1116
+ _logger.info("Gmail language settings updated (%.2fms)", _elapsed_ms(start))
1117
+ return _ok(start, creds, data)
1118
+
1119
+
1120
  @router.get("/settings/filters", response_model=GmailGenericResponse,
1121
  summary="List all filters")
1122
  async def list_filters(
 
1339
  return _ok(start, creds, {"deleted": send_as_email})
1340
 
1341
 
1342
+ @router.post("/settings/send-as/{send_as_email}/verify", response_model=GmailGenericResponse,
1343
+ summary="Send a verification email for a send-as alias (users.settings.sendAs.verify)")
1344
+ async def verify_send_as(
1345
+ send_as_email: str,
1346
+ creds: GmailCredentials = Depends(_credentials),
1347
+ service: GmailService = Depends(get_gmail_service),
1348
+ user_id: Optional[str] = None,
1349
+ ):
1350
+ start = time.perf_counter()
1351
+ try:
1352
+ await service.verify_send_as(creds, send_as_email, user_id=user_id or "me")
1353
+ except GmailAPIError as exc:
1354
+ raise _http_error(exc) from exc
1355
+ _logger.info("Gmail send-as %s verification triggered (%.2fms)", send_as_email, _elapsed_ms(start))
1356
+ return _ok(start, creds, {"verification_sent": send_as_email})
1357
+
1358
+
1359
  @router.get("/settings/delegates", response_model=GmailGenericResponse,
1360
  summary="List delegates")
1361
  async def list_delegates(
app/models/schemas.py CHANGED
@@ -1863,6 +1863,35 @@ class GmailLabelRequest(BaseModel):
1863
  color: Optional[Dict[str, str]] = Field(None, description="Label color (background/text color hex values)")
1864
 
1865
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1866
  class GmailMessageAttachmentMeta(BaseModel):
1867
  attachment_id: str = Field(..., description="Gmail attachment ID")
1868
  filename: str = Field("", description="Attachment file name")
 
1863
  color: Optional[Dict[str, str]] = Field(None, description="Label color (background/text color hex values)")
1864
 
1865
 
1866
+ class GmailLabelPatchRequest(BaseModel):
1867
+ """Partial update for a label (Gmail users.labels.patch)."""
1868
+
1869
+ name: Optional[str] = Field(None, min_length=1, max_length=255, description="New label name")
1870
+ label_list_visibility: Optional[str] = Field(None, pattern="^(labelShow|labelHide|labelShowIfUnread)$", description="Label list visibility")
1871
+ message_list_visibility: Optional[str] = Field(None, pattern="^(show|hide)$", description="Message list visibility")
1872
+ color: Optional[Dict[str, str]] = Field(None, description="Label color (background/text color hex values)")
1873
+
1874
+
1875
+ class GmailWatchRequest(BaseModel):
1876
+ """Set up push notifications for mailbox changes (users.watch)."""
1877
+
1878
+ topic_name: str = Field(..., min_length=1, description="Cloud Pub/Sub topic name, e.g. projects/{project}/topics/{topic}")
1879
+ label_ids: Optional[List[str]] = Field(None, description="Only send notifications for messages with these label IDs")
1880
+ label_filter_action: Optional[str] = Field(None, pattern="^(include|exclude)$", description="Whether labelIds are include or exclude filters")
1881
+ user_id: str = Field("me", description="Mailbox to watch (default 'me')")
1882
+
1883
+
1884
+ class GmailImportMessageRequest(BaseModel):
1885
+ """Import an RFC 2822 message directly into the mailbox (users.messages.import)."""
1886
+
1887
+ raw: str = Field(..., min_length=1, description="Base64url-encoded RFC 2822 message")
1888
+ label_ids: Optional[List[str]] = Field(None, description="Label IDs to apply on import")
1889
+ internal_date_source: Optional[str] = Field(None, pattern="^(dateHeader|receivedTime)$", description="Source for internal date")
1890
+ never_mark_spam: bool = Field(False, description="Ignore 'X-Gmail-Labels' / spam classifier and never mark as spam")
1891
+ process_for_calendar: bool = Field(False, description="Process the email for calendar events")
1892
+ delegate: str = Field("me", description="Mailbox to act on (default 'me')")
1893
+
1894
+
1895
  class GmailMessageAttachmentMeta(BaseModel):
1896
  attachment_id: str = Field(..., description="Gmail attachment ID")
1897
  filename: str = Field("", description="Attachment file name")
app/services/gmail_service.py CHANGED
@@ -493,6 +493,30 @@ class GmailService:
493
  creds, "POST", f"/users/{user_id}/messages", json_body=body
494
  )
495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  async def modify_message(
497
  self,
498
  creds: GmailCredentials,
@@ -566,6 +590,55 @@ class GmailService:
566
  f"/users/{user_id}/messages/{message_id}/attachments/{attachment_id}",
567
  )
568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  # ------------------------------------------------------------------
570
  # Drafts
571
  # ------------------------------------------------------------------
@@ -767,6 +840,17 @@ class GmailService:
767
  creds, "PUT", f"/users/{user_id}/labels/{label_id}", json_body=body
768
  )
769
 
 
 
 
 
 
 
 
 
 
 
 
770
  async def delete_label(
771
  self, creds: GmailCredentials, label_id: str, user_id: str = "me"
772
  ) -> None:
@@ -796,6 +880,36 @@ class GmailService:
796
  creds, "PUT", f"/users/{user_id}/settings/vacation", json_body=payload
797
  )
798
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
  async def list_filters(self, creds: GmailCredentials, user_id: str = "me") -> Dict[str, Any]:
800
  return await self._request(creds, "GET", f"/users/{user_id}/settings/filters")
801
 
@@ -870,6 +984,13 @@ class GmailService:
870
  creds, "PATCH", f"/users/{user_id}/settings/sendAs/{send_as_email}", json_body=payload
871
  )
872
 
 
 
 
 
 
 
 
873
  async def delete_send_as(
874
  self, creds: GmailCredentials, send_as_email: str, user_id: str = "me"
875
  ) -> None:
 
493
  creds, "POST", f"/users/{user_id}/messages", json_body=body
494
  )
495
 
496
+ async def import_message(
497
+ self,
498
+ creds: GmailCredentials,
499
+ raw: str,
500
+ user_id: str = "me",
501
+ *,
502
+ label_ids: Optional[List[str]] = None,
503
+ internal_date_source: Optional[str] = None,
504
+ never_mark_spam: bool = False,
505
+ process_for_calendar: bool = False,
506
+ ) -> Dict[str, Any]:
507
+ body: Dict[str, Any] = {"raw": raw}
508
+ if label_ids:
509
+ body["labelIds"] = label_ids
510
+ if internal_date_source:
511
+ body["internalDateSource"] = internal_date_source
512
+ if never_mark_spam:
513
+ body["neverMarkSpam"] = True
514
+ if process_for_calendar:
515
+ body["processForCalendar"] = True
516
+ return await self._request(
517
+ creds, "POST", f"/users/{user_id}/messages/import", json_body=body
518
+ )
519
+
520
  async def modify_message(
521
  self,
522
  creds: GmailCredentials,
 
590
  f"/users/{user_id}/messages/{message_id}/attachments/{attachment_id}",
591
  )
592
 
593
+ # ------------------------------------------------------------------
594
+ # Push notifications (watch / stop)
595
+ # ------------------------------------------------------------------
596
+
597
+ async def watch(
598
+ self,
599
+ creds: GmailCredentials,
600
+ user_id: str = "me",
601
+ *,
602
+ topic_name: str,
603
+ label_ids: Optional[List[str]] = None,
604
+ label_filter_action: Optional[str] = None,
605
+ ) -> Dict[str, Any]:
606
+ body: Dict[str, Any] = {"topicName": topic_name}
607
+ if label_ids:
608
+ body["labelIds"] = label_ids
609
+ if label_filter_action:
610
+ body["labelFilterAction"] = label_filter_action
611
+ return await self._request(creds, "POST", f"/users/{user_id}/watch", json_body=body)
612
+
613
+ async def stop_watch(self, creds: GmailCredentials, user_id: str = "me") -> None:
614
+ await self._request(creds, "POST", f"/users/{user_id}/stop")
615
+
616
+ # ------------------------------------------------------------------
617
+ # History
618
+ # ------------------------------------------------------------------
619
+
620
+ async def list_history(
621
+ self,
622
+ creds: GmailCredentials,
623
+ user_id: str = "me",
624
+ *,
625
+ start_history_id: int,
626
+ label_id: Optional[str] = None,
627
+ history_types: Optional[List[str]] = None,
628
+ max_results: Optional[int] = None,
629
+ page_token: Optional[str] = None,
630
+ ) -> Dict[str, Any]:
631
+ params: Dict[str, Any] = {"startHistoryId": start_history_id}
632
+ if label_id:
633
+ params["labelId"] = label_id
634
+ if history_types:
635
+ params["historyTypes"] = history_types
636
+ if max_results is not None:
637
+ params["maxResults"] = max_results
638
+ if page_token:
639
+ params["pageToken"] = page_token
640
+ return await self._request(creds, "GET", f"/users/{user_id}/history", params=params)
641
+
642
  # ------------------------------------------------------------------
643
  # Drafts
644
  # ------------------------------------------------------------------
 
840
  creds, "PUT", f"/users/{user_id}/labels/{label_id}", json_body=body
841
  )
842
 
843
+ async def patch_label(
844
+ self,
845
+ creds: GmailCredentials,
846
+ label_id: str,
847
+ payload: Dict[str, Any],
848
+ user_id: str = "me",
849
+ ) -> Dict[str, Any]:
850
+ return await self._request(
851
+ creds, "PATCH", f"/users/{user_id}/labels/{label_id}", json_body=payload
852
+ )
853
+
854
  async def delete_label(
855
  self, creds: GmailCredentials, label_id: str, user_id: str = "me"
856
  ) -> None:
 
880
  creds, "PUT", f"/users/{user_id}/settings/vacation", json_body=payload
881
  )
882
 
883
+ async def get_imap(self, creds: GmailCredentials, user_id: str = "me") -> Dict[str, Any]:
884
+ return await self._request(creds, "GET", f"/users/{user_id}/settings/imap")
885
+
886
+ async def update_imap(
887
+ self, creds: GmailCredentials, payload: Dict[str, Any], user_id: str = "me"
888
+ ) -> Dict[str, Any]:
889
+ return await self._request(
890
+ creds, "PUT", f"/users/{user_id}/settings/imap", json_body=payload
891
+ )
892
+
893
+ async def get_pop(self, creds: GmailCredentials, user_id: str = "me") -> Dict[str, Any]:
894
+ return await self._request(creds, "GET", f"/users/{user_id}/settings/pop")
895
+
896
+ async def update_pop(
897
+ self, creds: GmailCredentials, payload: Dict[str, Any], user_id: str = "me"
898
+ ) -> Dict[str, Any]:
899
+ return await self._request(
900
+ creds, "PUT", f"/users/{user_id}/settings/pop", json_body=payload
901
+ )
902
+
903
+ async def get_language(self, creds: GmailCredentials, user_id: str = "me") -> Dict[str, Any]:
904
+ return await self._request(creds, "GET", f"/users/{user_id}/settings/language")
905
+
906
+ async def update_language(
907
+ self, creds: GmailCredentials, payload: Dict[str, Any], user_id: str = "me"
908
+ ) -> Dict[str, Any]:
909
+ return await self._request(
910
+ creds, "PUT", f"/users/{user_id}/settings/language", json_body=payload
911
+ )
912
+
913
  async def list_filters(self, creds: GmailCredentials, user_id: str = "me") -> Dict[str, Any]:
914
  return await self._request(creds, "GET", f"/users/{user_id}/settings/filters")
915
 
 
984
  creds, "PATCH", f"/users/{user_id}/settings/sendAs/{send_as_email}", json_body=payload
985
  )
986
 
987
+ async def verify_send_as(
988
+ self, creds: GmailCredentials, send_as_email: str, user_id: str = "me"
989
+ ) -> None:
990
+ await self._request(
991
+ creds, "POST", f"/users/{user_id}/settings/sendAs/{send_as_email}/verify"
992
+ )
993
+
994
  async def delete_send_as(
995
  self, creds: GmailCredentials, send_as_email: str, user_id: str = "me"
996
  ) -> None: