Skip to main content

Async email scheduling, drafting, and Microsoft Graph mail integration.

Project description

YurMail

Librería Python para manejar correo personal de Outlook/Hotmail via Microsoft Graph API. Permite enviar, programar, y generar correos con y sin IA, sin necesidad de manejar HTML ni credenciales hardcodeadas.

from mail_scheduler import GraphAuthManager, GraphMailClient, text_to_html

auth   = GraphAuthManager(client_id="TU_CLIENT_ID", ...)
tokens = await auth.login()
mail   = GraphMailClient(tokens["access_token"])

await mail.send_mail(
    to=["amigo@outlook.com"],
    subject="Hola",
    body=text_to_html("Hola,\n\nTe escribo para confirmar la reunión.\n\nSaludos"),
)

Prerequisitos

1. Cuenta Microsoft personal

Necesitas una cuenta @outlook.com o @hotmail.com. Las cuentas institucionales (universitarias, empresariales) están administradas por un tercero y pueden requerir aprobación de su área de IT.

2. Registrar una aplicación en Microsoft Entra

Este paso es obligatorio y manual. Solo lo haces una vez. Microsoft requiere que cualquier aplicación que acceda a tu correo esté registrada.

  1. Ve a entra.microsoft.com e inicia sesión con tu cuenta personal
  2. Ve a Applications → App registrations → New registration
  3. Llena el formulario:
    • Name: el nombre que quieras (ej. mi-mail-scheduler)
    • Supported account types: Personal Microsoft accounts only
    • Redirect URI: selecciona plataforma Mobile and desktop applications y escribe http://localhost:8765/callback
  4. Haz clic en Register
  5. Copia el Application (client) ID — lo necesitarás después

Elige la plataforma "Mobile and desktop applications", no "Web". De lo contrario Microsoft exigirá un client_secret que no necesitas con esta librería.

3. Configurar permisos

En tu app registration recién creada:

  1. Ve a API Permissions → Add a permission → Microsoft Graph → Delegated
  2. Agrega estos permisos:
Permiso Para qué
offline_access Renovar el token sin pedir login cada hora
User.Read Obtener el perfil del usuario
Mail.ReadWrite Leer, mover y marcar mensajes
Mail.Send Enviar y responder correos
  1. Haz clic en Add permissions

No necesitas client_secret — la librería usa PKCE, el estándar para aplicaciones públicas.


Instalación

pip install -r requirements.txt

Configuración

Crea un archivo .env en tu proyecto (nunca lo subas a git):

MAILSCHEDULER_CLIENT_ID=tu-application-client-id
MAILSCHEDULER_TENANT=consumers
OPENAI_API_KEY=api-key

Uso básico

Login

import asyncio
from dotenv import load_dotenv
import os
from YurMail import GraphAuthManager, GraphMailClient

load_dotenv()

async def main():
    auth = GraphAuthManager(
        client_id=os.getenv("MAILSCHEDULER_CLIENT_ID"),
        tenant="consumers",
        redirect_uri="http://localhost:8765/callback",
        scopes=["offline_access", "User.Read", "Mail.ReadWrite", "Mail.Send"],
    )

    # Abre el browser una vez. El usuario inicia sesión y da consentimiento.
    tokens = await auth.login()
    mail   = GraphMailClient(tokens["access_token"])

asyncio.run(main())

La primera vez que corras login() Microsoft te mostrará una pantalla de consentimiento donde aceptas los permisos. Las siguientes veces usará el refresh_token para renovar el acceso sin abrir el browser.


Enviar un correo sin HTML

from mail_scheduler import text_to_html

await mail.send_mail(
    to=["destinatario@outlook.com"],
    subject="Reunión del viernes",
    body=text_to_html("""
        Hola Carlos,

        Te confirmo la reunión del viernes a las 10 AM.
        Será en la sala de juntas del tercer piso.

        Saludos,
        Ana
    """),
)

Enviar con adjunto

from mail_scheduler import attachment_from_bytes
from pathlib import Path

# Desde un archivo en disco
await mail.send_mail(
    to=["jefe@outlook.com"],
    subject="Reporte",
    body=text_to_html("Adjunto el reporte de esta semana."),
    attachments=["reporte.pdf"],
)

# Desde bytes en memoria (sin escribir a disco)
csv_bytes = generar_csv()
await mail.send_mail(
    to=["jefe@outlook.com"],
    subject="Datos",
    body=text_to_html("Adjunto los datos."),
    attachments=[attachment_from_bytes(csv_bytes, "datos.csv", "text/csv")],
)

Usar templates

from mail_scheduler import TEMPLATE_REPORTE, MailTemplate

# Template predefinido
subject, body = TEMPLATE_REPORTE.render(
    nombre="Gerente",
    periodo="Abril 2026",
    titulo="Ventas Q2",
    resumen="Crecimiento del 12% respecto al mes anterior.",
)
await mail.send_mail(to=["gerente@outlook.com"], subject=subject, body=body)

# Template propio
aviso = MailTemplate(
    name="aviso",
    subject="Aviso: {{ titulo }}",
    body="<p>Hola {{ nombre }},</p><p>{{ mensaje }}</p>",
)
subject, body = aviso.render(titulo="Cambio de horario", nombre="Equipo", mensaje="El lunes no hay clases.")

Guardar como borrador (para revisar antes de enviar)

draft = await mail.create_draft(
    to=["cliente@outlook.com"],
    subject="Propuesta comercial",
    body=text_to_html("Estimado cliente,\n\nAdjunto nuestra propuesta."),
)
# Aparece en tu carpeta Drafts de Outlook para revisarlo antes de enviarlo
print(f"Borrador guardado: {draft.id}")

# Cuando estés listo:
await mail.send_draft(draft.id)

Scheduler con IA

from YurMail import OpenAIDraftClient, Scheduler, InMemoryStore, EmailBuilder
from datetime import datetime, timedelta, timezone

llm       = OpenAIDraftClient(api_key=os.getenv("OPENAI_API_KEY"))
scheduler = Scheduler(mail_client=mail, llm_client=llm, store=InMemoryStore())

# Programa un correo: la IA genera el contenido justo antes de enviarlo
email = (
    EmailBuilder()
    .purpose("Recordar al equipo la entrega del proyecto del viernes")
    .to(["equipo@outlook.com"])
    .recipient_name("Equipo")
    .tone("casual")
    .language("es")
    .send_at(datetime.now(timezone.utc) + timedelta(hours=2))
    .build()
)
scheduler.schedule(email)
await scheduler.run_once()

Scheduler → borrador (revisar antes de enviar)

email = (
    EmailBuilder()
    .purpose("Responder al cliente sobre el retraso en la entrega")
    .to(["cliente@outlook.com"])
    .tone("formal")
    .send_at(datetime.now(timezone.utc) + timedelta(minutes=5))
    .save_as_draft()   # la IA genera el contenido pero va a Drafts
    .build()
)
scheduler.schedule(email)
await scheduler.run_once()
# Revisa tu carpeta Drafts, edita si necesitas, y envía desde Outlook

Referencia rápida

Función Para qué
GraphAuthManager.login() Login OAuth con browser
GraphAuthManager.refresh_access_token(token) Renovar token expirado
GraphMailClient.get_me() Perfil del usuario autenticado
GraphMailClient.list_messages(top, folder, only_unread) Listar mensajes
GraphMailClient.get_message(id) Mensaje completo con body HTML
GraphMailClient.send_mail(to, subject, body, ...) Enviar correo
GraphMailClient.create_draft(to, subject, body, ...) Guardar borrador
GraphMailClient.send_draft(draft_id) Enviar borrador existente
GraphMailClient.reply_to_message(id, body) Responder manteniendo hilo
GraphMailClient.mark_as_read(id) Marcar como leído
GraphMailClient.move_to_trash(id) Mover a eliminados
text_to_html(texto) Convertir texto plano a HTML
attachment_from_bytes(data, filename) Adjunto desde memoria
MailTemplate.render(**kwargs) Renderizar template
EmailBuilder().purpose().to().send_at().build() Construir correo programado
Scheduler.schedule(email) Agregar correo al scheduler
Scheduler.run_once() Procesar correos vencidos
Scheduler.run_loop(interval_seconds) Bucle continuo

Estructura del proyecto

YurMail/
├── authentication_microsoft/   OAuth2 + PKCE contra Microsoft
├── mail/                       Cliente Graph + templates + adjuntos
├── LLM/                        Generación de borradores con OpenAI
└── scheduler/                  Programación y recurrencia de envíos

Notas

  • El access_token expira en 1 hora. Usa refresh_access_token() para renovarlo sin pedir login de nuevo.
  • El refresh_token expira en 90 días sin uso. Si expira, el usuario debe hacer login() de nuevo.
  • El CLIENT_ID no es un secreto — puedes compartirlo. Lo que nunca debes compartir son los tokens.
  • Esta librería es solo para cuentas personales de Microsoft (@outlook.com, @hotmail.com). Para cuentas corporativas el proceso de registro es diferente.

Tutorial interactivo

Prueba YurMail directamente en tu navegador sin instalar nada:

Open in Colab

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yurmail-0.2.0.tar.gz (35.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yurmail-0.2.0-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

Details for the file yurmail-0.2.0.tar.gz.

File metadata

  • Download URL: yurmail-0.2.0.tar.gz
  • Upload date:
  • Size: 35.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for yurmail-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d36c5b285d2da52e8a18c49afaa7500e098f244749dd2f9e0cedb05d35d802f3
MD5 de857f68e284cbe9a68b1111f848a240
BLAKE2b-256 cc385cecbac25ed15a3056d711d49092b7db6837722ce89939a0e20c86185971

See more details on using hashes here.

File details

Details for the file yurmail-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: yurmail-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for yurmail-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e0bed5984c33a0eae62fc7e781bb9807ca538904f202f7b3dbc4d6390d8001a
MD5 44609035b6b9715e5fb29f717c7267dd
BLAKE2b-256 9b0ba9cff92c4d2b9f6b0ba6be0755a295008bea4b932cfa8c86e89c19400a45

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page