Skip to main content

AUTH LOGIN manual in Python with smtplib: special characters and error 535

Rogelio Guerra Riverón
Author
Rogelio Guerra Riverón
Building my own web infrastructure from scratch. Here I document each step: servers, networks, containers and everything that comes along.

The Problem
#

A few days ago I tried to automate sending emails from my home server. Nothing complicated: a Python script with smtplib to send notifications. The problem came when I configured a password with special characters: MiPasw0rd$Ñ.

import smtplib

server = smtplib.SMTP('mail.example.com', 587)
server.starttls()
server.login('usuario@example.com', 'MiPasw0rd$Ñ')  # Error 535

Result: SMTPAuthenticationError: (535, b'5.7.8 Authentication credentials invalid')

The strange thing is that the password was correct. I could access it manually without problems. Error 535 suggested authentication failures, but the real problem was in the encoding.

Why It Fails
#

The fault lies in smtplib’s encoding handling. The login() method uses UTF-8 by default, but then applies transformations that don’t always respect special characters correctly, especially when the server or library have legacy configurations.

In my case, the SMTP server expected consistent UTF-8 encoding. When smtplib processed the password with $ and Ñ, something along the way got corrupted.

The Solution: Manual AUTH LOGIN
#

The AUTH LOGIN protocol is simple: username and password are encoded in base64 separately and sent in manual steps. This gives you complete control over the encoding.

import smtplib
import base64

def auth_login_manual(server, usuario, contraseña):
    """Implementa AUTH LOGIN manualmente con UTF-8"""
    
    # Respuesta inicial del servidor
    code, response = server.docmd("AUTH LOGIN")
    
    # Codificar usuario en base64 UTF-8
    usuario_b64 = base64.b64encode(usuario.encode('utf-8')).decode('ascii')
    code, response = server.docmd(usuario_b64)
    
    # Codificar contraseña en base64 UTF-8
    contraseña_b64 = base64.b64encode(contraseña.encode('utf-8')).decode('ascii')
    code, response = server.docmd(contraseña_b64)
    
    # Verificar éxito (código 235 es autenticación exitosa)
    if code != 235:
        raise Exception(f"Autenticación fallida: {code} {response}")
    
    return True

# Uso
server = smtplib.SMTP('mail.example.com', 587)
server.starttls()
auth_login_manual(server, 'usuario@example.com', 'MiPasw0rd$Ñ')
print("Autenticado exitosamente")

Protocol Breakdown
#

  1. AUTH LOGIN: The client requests to use the AUTH LOGIN mechanism
  2. Username in base64: The server responds with 334, expects the encoded username
  3. Password in base64: The server responds with 334, expects the encoded password
  4. Response 235: Indicates successful authentication

The docmd() method sends raw SMTP commands and returns the response code and message.

Testing on My Server
#

I implemented this in my home infrastructure with Postfix. The difference is noticeable:

# Antes: falla con caracteres especiales
server.login('admin@local.home', 'P@ssw0rdÑ')  # Error 535

# Después: funciona perfectamente
auth_login_manual(server, 'admin@local.home', 'P@ssw0rdÑ')  # OK

Security Considerations
#

  • Base64 is not encryption: always use STARTTLS or direct connection to port 465 with SSL
  • UTF-8 encoding is safe for special characters
  • This method is compatible with any SMTP server that supports AUTH LOGIN

Complete Script
#

import smtplib
import base64

class SMTPConnection:
    def __init__(self, host, port=587):
        self.server = smtplib.SMTP(host, port)
        self.server.starttls()
    
    def login(self, usuario, contraseña):
        self.server.docmd("AUTH LOGIN")
        self.server.docmd(base64.b64encode(usuario.encode('utf-8')).decode('ascii'))
        code, resp = self.server.docmd(base64.b64encode(contraseña.encode('utf-8')).decode('ascii'))
        if code != 235:
            raise Exception(f"Auth failed: {code}")
    
    def send(self, de, para, asunto, cuerpo):
        msg = f"From: {de}\nTo: {para}\nSubject: {asunto}\n\n{cuerpo}"
        self.server.sendmail(de, para, msg)
    
    def close(self):
        self.server.quit()

# Uso
smtp = SMTPConnection('mail.example.com')
smtp.login('usuario@example.com', 'MiPasw0rd$Ñ')
smtp.send('usuario@example.com', 'destino@example.com', 'Test', 'Funciona!')
smtp.close()

Conclusion
#

If your SMTP server rejects passwords with special characters, it’s not a mystery. It’s an encoding problem. Implementing AUTH LOGIN manually gives you complete control and works with any password.

I deployed it in production on my home server and haven’t had any problems since.


Recommended Equipment#

Affiliate links. No extra cost to you.