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 YurMail 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 YurMail 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 YurMail 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 YurMail 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.1.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.1-py3-none-any.whl (29.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: yurmail-0.2.1.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.1.tar.gz
Algorithm Hash digest
SHA256 5e3de842dfdf2df1976686bf30b826de6928a2d45b7e8b9142634e140a8cdf40
MD5 42cb1b79c81e432a968d3221e36f6ed0
BLAKE2b-256 271c2193dbdd1b0debe6b48e2fc40683fb546ac51de236eb90e20aa0a1c4da97

See more details on using hashes here.

File details

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

File metadata

  • Download URL: yurmail-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 29.5 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 526ebe6c7e470105addb596bffb8d49adc707a9d2cb9f4741f7338c027478e41
MD5 2202fbf01c6d19b226d30f4841c936b6
BLAKE2b-256 cca19662e34ba6a9c38030bcc41f47e6b69d87d85b75d8ebd974cff5badeb348

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