Send email with Python and Gmail: SMTP, OAuth and attachments

The idea is straightforward: the following snippet shows how email delivery works in Python through the smtplib library. If you prefer a video tutorial, visit the following URL: https://www.youtube.com/watch?v=vCFioQizM4w

You can download the full code from the following URL https://github.com/al118345/envio_email

The original code is intentionally simple: gmail_user identifies the sender and the credential is read from a local Python module. Treat that module as a historical example; never commit a password or app password to the repository. The subject and body are then assembled before opening the SMTP connection.

Finally, from line 26 onwards we establish the connection with the Gmail SMTP server. An SMTP server is the machine responsible for email delivery, acting as an electronic mail carrier over the Internet.

If you do not use Gmail, replace the host, port, encryption and authentication mechanism with the settings documented by your provider. Microsoft 365 and current Outlook/Hotmail configurations normally use smtp.office365.com with STARTTLS on port 587.

Gmail authentication in 2026

The screenshots below show the retired “less secure apps” workflow and are kept only as historical context. Do not try to enable that option: Google no longer supports username-and-account-password access for this kind of integration.

For a small personal script, an app password may work after enabling 2-Step Verification, although Google recommends OAuth whenever possible. App passwords are not available for every account type and are revoked when the Google Account password changes.

For a multi-user or production integration, use OAuth 2.0/XOAUTH2 or a transactional email provider. Google Workspace administrators can also configure SMTP Relay; since 2025, Workspace no longer accepts password-only access from less secure apps.

Official Gmail authentication references:App passwords, OAuth 2.0 for Gmail SMTP and Google Workspace SMTP options.

1 Sign in to your Google account through Gmail
1 Sign in to your Google account through Gmail
2 Open the security menu
2 Open the security menu
3 Historical screen for less secure apps: no longer available
3 Historical screen for less secure apps: no longer available

App password example for a small automation

The original example is useful to understand SMTP, but a real project must not write credentials in source code. Use environment variables or a secret manager. For Gmail, prefer OAuth 2.0; reserve an app password for a compatible account and a small automation that cannot use Sign in with Google.

The EmailMessage class also makes the code clearer because it separates subject, sender, recipient and body without manually building the raw message string.

Sending an attachment

If you need to send invoices, reports or generated PDFs, build the message with EmailMessage and attach the file as bytes. This keeps the code readable and avoids many encoding errors.

SMTP settings by provider

  • Gmail: smtp.gmail.com, SSL port 465 or STARTTLS port 587.
  • Outlook or Hotmail: smtp.office365.com, usually STARTTLS port 587.
  • Custom hosting: check the SMTP server, port and encryption mode in the hosting control panel.

Common errors

  • Authentication failed: verify the user, application password and two-step verification.
  • Connection timeout: check port, firewall and whether your hosting provider blocks SMTP.
  • Accents or special characters appear broken: use EmailMessage and UTF-8 content.

Production checklist

For a real application, email sending should normally live in the backend instead of the browser. The backend can protect SMTP credentials, validate recipients, apply rate limits and store delivery errors for later review.

If the email is triggered from Angular, expose a small API endpoint and let Python handle the SMTP connection. That architecture is safer than sending credentials to the frontend.

Deliverability and operational details

Sending an email successfully from Python does not guarantee that the message will reach the inbox. For a real domain, configure SPF, DKIM and DMARC records, use a recognizable sender and avoid changing the From address dynamically. Those DNS records help mailbox providers verify that the server is allowed to send on behalf of the domain.

It is also useful to store a delivery log with timestamp, recipient, provider response and internal identifier. Do not store the full message body unless the business case requires it. A small log makes it possible to retry temporary failures, detect blocked accounts and explain what happened when a user says they did not receive a notification.

Recommended architecture with Angular

In an Angular application, the browser should only send a request such as “send invoice” or “notify user”. The backend validates permissions, builds the message, reads templates, attaches files and talks to SMTP. This separation protects credentials and keeps email rules in one testable place.

Templates and retries

For maintainable projects, keep message templates outside the sending function and pass explicit variables such as user name, invoice number or confirmation link. Add retry rules only for temporary provider errors, never for invalid addresses or authentication failures. That keeps automation predictable and prevents accidental spam loops.

Related reading: secure Angular API with Nginx and FastAPI. proteger API Angular, Nginx y FastAPI.