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.

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.1.1.tar.gz (35.4 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.1.1-py3-none-any.whl (29.4 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for yurmail-0.1.1.tar.gz
Algorithm Hash digest
SHA256 5ff2ae8d9b205ed2974cf21c2736dbb465204498875dd3436de134134f4af8a7
MD5 4c5a5bc308e2a433629123a0a215787d
BLAKE2b-256 fddb8d12fef1341cb057a605c567f1fcc0d64dee1efcc208695a6908491f417d

See more details on using hashes here.

File details

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

File metadata

  • Download URL: yurmail-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 29.4 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.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e7a8e12e2a493d4d48b8f8bf7f538e7cf1f1736830bfa5695afed440dd7c515a
MD5 1205cb369f92df44f9f9798882287936
BLAKE2b-256 31b7eaf4ec40ea33230e37ace997c5c19db68445891663e7908f6e21ada36c40

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