--- name: imap_save_draft kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def imap_save_draft(conn, raw_rfc822: bytes, mailbox: str = '[Gmail]/Drafts', flags: str = '\\Draft') -> dict" description: "Guarda un borrador en un mailbox via IMAP APPEND sobre una conexion imaplib.IMAP4_SSL ya autenticada. Ejecuta conn.append(mailbox, flags, imaplib.Time2Internaldate(time.time()), raw_rfc822): raw_rfc822 son los bytes MIME ya serializados de un email completo (cabeceras + cuerpo) que el caller arma con email.message.EmailMessage().as_bytes() (stdlib) o con las funciones email_build_*_py_infra del registry + serializacion. A diferencia de las demas operaciones del grupo, APPEND NO requiere un mailbox seleccionado: el destino es el argumento mailbox (default '[Gmail]/Drafts', con su prefijo [Gmail]/). flags default '\\Draft' para que el cliente lo trate como borrador. Valida que raw_rfc822 sean bytes. No abre la conexion ni resuelve credenciales. Nunca lanza: devuelve {status:'ok', mailbox} o {status:'error', error}; tambien error si APPEND responde un typ distinto de OK. Parte del grupo email/imap. Solo stdlib (imaplib, time, email.message para construir el MIME)." tags: [email, imap, mail, draft, append, infra] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_py_core" imports: [imaplib, time] params: - name: conn desc: "objeto imaplib.IMAP4_SSL (o IMAP4) YA conectado y autenticado. Normalmente lo produce imap_connect. APPEND no requiere mailbox seleccionado: el destino se pasa en el argumento mailbox. La funcion no abre ni cierra conn." - name: raw_rfc822 desc: "bytes MIME ya serializados de un email completo (cabeceras From/To/Subject + cuerpo). El caller los construye con email.message.EmailMessage(...).as_bytes() (stdlib) o con email_build_*_py_infra del registry + serializacion. Debe ser bytes; un str devuelve {status:'error'}." - name: mailbox desc: "mailbox destino del borrador. Default '[Gmail]/Drafts' (carpeta de borradores de Gmail, con prefijo [Gmail]/). En otros servidores suele ser 'Drafts'. Debe existir en el servidor." - name: flags desc: "banderas IMAP a poner al mensaje, como string separado por espacios. Default '\\Draft' para marcarlo como borrador. Combinable, p.ej. '\\Draft \\Seen'." output: "dict. En exito: {status:'ok', mailbox:str} reflejando el mailbox donde se guardo el borrador. En error (sin lanzar): {status:'error', error:str}, p.ej. si raw_rfc822 no son bytes o si APPEND responde un typ distinto de OK." tested: false tests: [] test_file_path: "" file_path: "python/functions/infra/imap_save_draft.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from email.message import EmailMessage from infra.imap_connect import imap_connect from infra.imap_save_draft import imap_save_draft # conn ya conectado y autenticado (lo produce imap_connect). APPEND no necesita select(). conn = imap_connect(...)["conn"] # firma exacta la define imap_connect # Armar un borrador minimo con stdlib y serializarlo a bytes MIME. msg = EmailMessage() msg["From"] = "yo@example.com" msg["To"] = "destinatario@example.com" msg["Subject"] = "Propuesta (borrador)" msg.set_content("Hola, este es un borrador guardado por IMAP APPEND.") raw = msg.as_bytes() # Guardarlo en la carpeta de borradores de Gmail (nota el prefijo [Gmail]/). print(imap_save_draft(conn, raw, mailbox="[Gmail]/Drafts")) # {'status': 'ok', 'mailbox': '[Gmail]/Drafts'} ``` ## Cuando usarla Cuando quieres dejar un correo a medio escribir guardado en el servidor (no enviarlo) para retomarlo desde cualquier cliente: el clasico "guardar borrador". Util para flujos donde un agente prepara una respuesta y la deja en Drafts para que el humano la revise y envie. Tambien sirve para archivar copias arbitrarias de mensajes en un mailbox (cambiando `flags`). El envio real es otra cosa: para enviar usa `smtp_send_py_infra`. Compone con `email_build_*_py_infra` (que producen el EmailMessage) + serializacion a bytes. ## Gotchas - **Impura**: escribe un mensaje nuevo en el servidor (consume cuota de la cuenta). - **raw_rfc822 son BYTES, no str**: el mensaje MIME debe estar ya serializado a `bytes` (`EmailMessage().as_bytes()`). Pasar un `str` devuelve `{status:'error'}`. La funcion no construye el MIME: solo lo deposita. - **APPEND no usa mailbox seleccionado**: a diferencia de STORE/COPY/EXPUNGE, el destino de APPEND es el argumento `mailbox`, no el mailbox que el caller selecciono. No hace falta `conn.select(...)` previo. - **Prefijo [Gmail]/ y existencia**: en Gmail la carpeta de borradores es `[Gmail]/Drafts` (con prefijo). En otros servidores suele ser `Drafts`. El mailbox destino debe existir; si no, APPEND falla y se devuelve error. - **No hay UID estable garantizado en la respuesta**: APPEND crea un mensaje nuevo; algunos servidores devuelven su UID (APPENDUID) y otros no. Esta funcion no lo parsea — devuelve solo `{status, mailbox}`. Si necesitas el UID del borrador, busca despues con `imap_search`. - **flags con backslash**: las banderas del sistema llevan barra invertida (`\\Draft`, `\\Seen`). En el string Python recuerda escaparla (`"\\Draft"`). - **Nunca lanza**: cualquier fallo (mailbox inexistente, conexion caida, bytes invalidos, respuesta no-OK) vuelve como `{status:'error', error:str}`.