Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/lffiesco-svg/gastromovil/llms.txt

Use this file to discover all available pages before exploring further.

GastroMóvil sends all user-facing transactional emails through Resend, a developer-focused email API. The integration lives in usuarios/views.py and is a thin wrapper around resend.Emails.send(). A separate legacy SMTP backend (SSLEmailBackend) handles outbound mail from the contacto app’s contact form. Real-time order status notifications are delivered over WebSocket via Django Channels — those are not email-based and are covered in the Delivery Tracking documentation.

Resend Integration

All transactional messages are sent from a single helper function defined at the top of usuarios/views.py:
import resend
from django.conf import settings

def enviar_email_resend(to_email, subject, html):
    try:
        resend.api_key = settings.RESEND_API_KEY
        resend.Emails.send({
            "from": "GastroWeb <noreply@gastromovil.online>",
            "to": [to_email],
            "subject": subject,
            "html": html,
        })
        return True
    except Exception as e:
        print(f"Error enviando email: {e}")
        return False
The from address is always GastroWeb <noreply@gastromovil.online>. The function returns True on success and False on any exception, making it safe to call from background threads without crashing the request cycle.
The RESEND_API_KEY setting is loaded from the environment via config('RESEND_API_KEY') in settings.py. The API key is injected into resend.api_key on every call so the module-level global is always fresh — useful if the key is rotated without restarting the server.

Transactional Emails

1 — Registration Verification

When a new user submits the sign-up form, a 6-digit numeric code is generated with random.randint(100000, 999999) and stored in the session. The code is emailed immediately and must be entered on the verification page before the Usuario record is created.
codigo = str(random.randint(100000, 999999))
request.session['codigo_verificacion'] = codigo

html = get_email_html(
    titulo='Código de verificación',
    contenido_central=
        parrafo(f'Hola <strong>{first_name}</strong>, gracias por registrarte '
                f'en GastroWeb. Usa este código para verificar tu cuenta:') +
        codigo_box(codigo) +
        nota('Este código expira en 10 minutos. No lo compartas con nadie.')
)

enviar_email_resend(
    to_email=email,
    subject='Código de verificación - Gastroweb',
    html=html,
)
The code expires when the Django session expires (30 minutes maximum, but the email copy tells the user 10 minutes). No CodigoRecuperacion model is used here — the code lives only in the session.

2 — Login Alert

Every successful password-based login triggers a background email alerting the user to the new session. The email is sent in a daemon thread so it does not block the redirect:
def enviar_correo():
    ip = request.META.get('REMOTE_ADDR', 'desconocida')
    html = get_email_html(
        titulo='Inicio de sesión exitoso',
        contenido_central=
            parrafo(f'Hola <strong>{user.first_name}</strong>, has iniciado sesión '
                    f'en <strong>GastroWeb</strong>.') +
            parrafo('Si no fuiste tú, cambia tu contraseña de inmediato.') +
            nota(f'IP: {ip} | Fecha: {user.last_login.strftime("%d/%m/%Y %H:%M")} UTC')
    )
    enviar_email_resend(
        to_email=user.email,
        subject='Inicio de sesión exitoso - Gastroweb',
        html=html,
    )

threading.Thread(target=enviar_correo, daemon=True).start()
The IP address is read from request.META['REMOTE_ADDR'] and included in the footer note alongside the login timestamp.

3 — Password Recovery (6-digit code)

Password recovery uses the CodigoRecuperacion model to persist the 6-digit code in the database with a 10-minute expiry. Two entry points exist: a REST API endpoint (enviar_codigo) used by the mobile flow and a web endpoint (enviar_codigo_web) used by the browser flow.
# Web flow (enviar_codigo_web)
CodigoRecuperacion.objects.filter(usuario=usuario).delete()
codigo = str(secrets.SystemRandom().randint(100000, 999999))
CodigoRecuperacion.objects.create(usuario=usuario, codigo=codigo)

html = get_email_html(
    titulo='Recuperación de contraseña',
    contenido_central=
        parrafo('Usa el siguiente código para restablecer tu contraseña:') +
        codigo_box(codigo) +
        nota('Este código expira en 10 minutos.')
)

enviar_email_resend(
    to_email=usuario.email,
    subject='Recuperar contraseña - Gastroweb',
    html=html,
)
Any previously issued codes for that user are deleted before a new one is created, so only one valid code can exist at a time.

4 — Password Changed Confirmation

After a successful password reset, a plain-text confirmation is dispatched asynchronously:
enviar_email_resend(
    to_email=usuario.email,
    subject='Cambio de contraseña exitoso - Gastroweb',
    html=f'<p>Hola {usuario.username}, tu contraseña fue cambiada exitosamente.</p>',
)

HTML Email Templates

All branded emails are built with helper functions from usuarios/email_template.py. The template produces a single-column responsive table layout with a crimson (#c0392b) header and a GastroWeb logo emoji.
HelperPurpose
get_email_html(titulo, contenido_central)Outer wrapper — header, title row, content area, and footer
codigo_box(codigo)Renders the 6-digit code in a large dashed box
parrafo(texto)Body paragraph with #555555 text at 15 px / 1.7 line-height
nota(texto)Small centred footnote in #aaaaaa at 13 px
Example of a composed email body:
from usuarios.email_template import get_email_html, codigo_box, parrafo, nota

html = get_email_html(
    titulo='Código de verificación',
    contenido_central=(
        parrafo('Usa este código para verificar tu cuenta:') +
        codigo_box('847291') +
        nota('Este código expira en 10 minutos. No lo compartas con nadie.')
    )
)

Legacy SMTP Backend (Contact Form)

The contacto app sends outbound mail through a custom SSLEmailBackend that wraps Django’s built-in SMTP backend. The backend opens a plain SMTP connection on port 465, upgrades it to TLS via STARTTLS, and authenticates with the configured Gmail credentials.
# usuarios/backends.py
class SSLEmailBackend(SMTPEmailBackend):
    def open(self):
        if self.connection:
            return False
        try:
            self.connection = smtplib.SMTP(self.host, self.port, timeout=self.timeout)
            self.connection.ehlo()
            if self.use_tls:
                ctx = ssl.create_default_context(cafile=certifi.where())
                self.connection.starttls(context=ctx)
                self.connection.ehlo()
            if self.username and self.password:
                self.connection.login(self.username, self.password)
            return True
        except Exception:
            if not self.fail_silently:
                raise
            return False
Settings that power this backend:
EMAIL_BACKEND  = 'usuarios.backends.SSLEmailBackend'
EMAIL_HOST     = 'smtp.gmail.com'
EMAIL_PORT     = 465
EMAIL_USE_TLS  = True
EMAIL_USE_SSL  = False
EMAIL_HOST_USER     = 'johanapalacio763@gmail.com'
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')

CONTACTO_EMAIL           = 'ospinacadenaoscar@gmail.com'
CONTACTO_EMAIL_PASSWORD  = config('CONTACTO_EMAIL_PASSWORD')
The legacy SMTP backend is used only by the contacto app. All user account emails (verification, login alerts, password recovery) go through Resend. Do not redirect user account emails to the SMTP backend — it does not support the noreply@gastromovil.online sender address.

Real-Time Order Notifications

Order status changes (e.g., accepted, picked up, delivered) are pushed to the browser over WebSocket using Django Channels with an in-memory channel layer. This is entirely separate from email. No SMTP or Resend call is made when an order changes state.
The WebSocket consumer and channel routing are documented in the Delivery Tracking section of this documentation.

Build docs developers (and LLMs) love