Unleash Your Inner Robot: Automate Gmail Attachments with Python!

Introduction

Ever find yourself repeatedly attaching the same file to different emails? Or perhaps you need to send automated reports with a specific attachment every week? Imagine a world where your computer handles this tedious task for you. Welcome to that world! In this blog post, we’ll dive into how you can use Python to automate sending emails with attachments via Gmail. It’s easier than you think and incredibly powerful for boosting your productivity and freeing up your time for more important tasks.

Why Automate Email Attachments?

Automating email attachments isn’t just a cool party trick; it offers practical benefits:

  • Time-Saving: Say goodbye to manual clicks and browsing for files. Automation handles it instantly.
  • Error Reduction: Eliminate human errors like forgetting an attachment or sending the wrong file.
  • Batch Sending: Send the same attachment to multiple recipients effortlessly, personalizing each email if needed.
  • Automated Reports: Integrate this script with other tools to send daily, weekly, or monthly reports that include generated files, without any manual intervention.
  • Consistency: Ensure that emails and attachments always follow a predefined format and content.

What You’ll Need

Before we start coding, let’s gather our tools. Don’t worry, everything listed here is free and widely available:

  • Python 3: Make sure you have Python installed on your computer. You can download the latest version from python.org.
  • A Google Account: This is essential for accessing Gmail and its API.
  • Google Cloud Project: We’ll need to set up a project in Google Cloud Console to enable the Gmail API and get the necessary credentials.
  • Python Libraries: We’ll use a few specific Python libraries to interact with Google’s services:
    • google-api-python-client: This library helps us communicate with various Google APIs, including Gmail.
    • google-auth-oauthlib and google-auth-httplib2: These are for handling the secure authentication process with Google.

Let’s install these Python libraries using pip, Python’s package installer:

pip install google-api-python-client google-auth-oauthlib google-auth-httplib2

What is an API?
An API (Application Programming Interface) is like a menu in a restaurant. It tells you what actions you can “order” (e.g., send an email, read a calendar event) and what information you need to provide for each order. In our case, the Gmail API allows our Python script to programmatically “order” actions like sending emails from your Gmail account, without having to manually open the Gmail website.

Step 1: Setting Up Your Google Cloud Project

This is a crucial step to allow your Python script to securely communicate with Gmail. It might seem a bit involved, but just follow the steps carefully!

1. Go to Google Cloud Console

Open your web browser and navigate to the Google Cloud Console. You’ll need to log in with your Google account.

2. Create a New Project

  • At the top of the Google Cloud Console page, you’ll usually see a project dropdown (it might say “My First Project” or your current project’s name). Click on it.
  • In the window that appears, click “New Project.”
  • Give your project a meaningful name (e.g., “Gmail Automation Project”) and click “Create.”

3. Enable the Gmail API

  • Once your new project is created and selected (you can choose it from the project dropdown if it’s not already selected), use the search bar at the top of the Google Cloud Console.
  • Type “Gmail API” and select “Gmail API” from the results.
  • On the Gmail API page, click the “Enable” button.

4. Create Credentials (OAuth 2.0 Client ID)

This step gives your script permission to access your Gmail.

  • From the left-hand menu, navigate to “APIs & Services” > “Credentials.”
  • Click “Create Credentials” and choose “OAuth client ID.”
  • Consent Screen: If prompted, you’ll first need to configure the OAuth Consent Screen. This screen is what users see when they grant your app permission.
    • Select “External” for User Type and click “Create.”
    • Fill in the required information: “App name” (e.g., “Python Gmail Sender”), your “User support email,” and your email under “Developer contact information.” You don’t need to add scopes for now. Click “Save and Continue.”
    • For “Test users,” click “Add Users” and add your own Gmail address (the one you’re using for this project). This allows you to test your application. Click “Save and Continue.”
    • Review the summary and click “Back to Dashboard.”
  • Now, go back to “Create Credentials” > “OAuth client ID” (if you were redirected away).
    • For “Application type,” select “Desktop app.”
    • Give it a name (e.g., “Gmail_Automation_Desktop”).
    • Click “Create.”
  • A window will pop up showing your client ID and client secret. Click “Download JSON” and save the file as credentials.json. It’s very important that this credentials.json file is saved in the same directory where your Python script will be.

What is OAuth 2.0?
OAuth 2.0 is an industry-standard protocol for authorization. In simple terms, it’s a secure way for an application (our Python script) to access certain parts of a user’s account (your Gmail) without ever seeing or storing the user’s password. Instead, it uses temporary “tokens” to grant specific, limited permissions. The credentials.json file contains the unique identifiers our script needs to start this secure conversation with Google.

Step 2: Writing the Python Code

Now for the fun part! Open your favorite code editor (like VS Code, Sublime Text, or even Notepad) and let’s start writing our Python script.

1. Imports and Setup

We’ll begin by importing the necessary libraries. These modules provide the tools we need for sending emails, handling files, and authenticating with Google.

import os
import pickle
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

2. Authentication Function

This function handles the secure login process with your Google account. The first time you run the script, it will open a browser window for you to log in and grant permissions. After that, it saves your authentication information in a file called token.pickle, so you don’t have to re-authenticate every time you run the script.

def authenticate_gmail():
    """Shows user how to authenticate with Gmail API and stores token.
    The file token.pickle stores the user's access and refresh tokens, and is
    created automatically when the authorization flow completes for the first
    time.
    """
    creds = None
    # Check if a token file already exists.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)

    # If there are no (valid) credentials available, or they have expired,
    # let the user log in or refresh the existing token.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            # If credentials are expired but we have a refresh token, try to refresh them.
            creds.refresh(Request())
        else:
            # Otherwise, initiate the full OAuth flow.
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            # This line opens a browser for the user to authenticate.
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run, so we don't need to re-authenticate.
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    # Build the Gmail service object using the authenticated credentials.
    service = build('gmail', 'v1', credentials=creds)
    return service

3. Creating the Email Message with Attachment

This function will build the email, including the subject, body, sender, recipient, and the file you want to attach.

def create_message_with_attachment(sender, to, subject, message_text, file_path):
    """Create a message for an email with an attachment."""
    message = MIMEMultipart() # MIMEMultipart allows us to combine different parts (text, attachment) into one email.
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject

    # Attach the main body text of the email
    msg = MIMEText(message_text)
    message.attach(msg)

    # Attach the file
    try:
        with open(file_path, 'rb') as f: # Open the file in binary read mode ('rb')
            part = MIMEBase('application', 'octet-stream') # Create a new part for the attachment
            part.set_payload(f.read()) # Read the file's content and set it as the payload
        encoders.encode_base64(part) # Encode the file content to base64, which is standard for email attachments.

        # Extract filename from the provided path to use as the attachment's name.
        file_name = os.path.basename(file_path)
        part.add_header('Content-Disposition', 'attachment', filename=file_name)
        message.attach(part) # Attach the file part to the overall message.
    except FileNotFoundError:
        print(f"Error: Attachment file not found at '{file_path}'. Sending email without attachment.")
        # If the file isn't found, we'll still send the email body without the attachment.
        pass

    # Encode the entire message into base64 URL-safe format for the Gmail API.
    raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
    return {'raw': raw_message}

4. Sending the Message

This function takes the authenticated Gmail service and the email message you’ve created, then uses the Gmail API to send it.

def send_message(service, user_id, message):
    """Send an email message.

    Args:
        service: Authorized Gmail API service instance.
        user_id: User's email address. The special value "me" can be used to indicate the authenticated user.
        message: A dictionary containing the message to be sent, created by create_message_with_attachment.

    Returns:
        The sent message object if successful, None otherwise.
    """
    try:
        # Use the Gmail API's 'users().messages().send' method to send the email.
        sent_message = service.users().messages().send(userId=user_id, body=message).execute()
        print(f"Message Id: {sent_message['id']}")
        return sent_message
    except HttpError as error:
        print(f"An error occurred while sending the email: {error}")
        return None

5. Putting It All Together (Main Script)

Finally, let’s combine these functions into a main block that will execute our automation logic. This is where you’ll define the sender, recipient, subject, body, and attachment file.

def main():
    # 1. Authenticate with Gmail API
    service = authenticate_gmail()

    # 2. Define email details
    sender_email = "me"  # "me" refers to the authenticated user's email address
    recipient_email = "your-email@example.com" # !!! IMPORTANT: CHANGE THIS TO YOUR ACTUAL RECIPIENT'S EMAIL ADDRESS !!!
    email_subject = "Automated Daily Report - From Python!"
    email_body = (
        "Hello Team,\n\n"
        "Please find the attached daily report for your review. This email "
        "was automatically generated by our Python script.\n\n"
        "Best regards,\n"
        "Your Friendly Automation Bot"
    )

    # Define the attachment file.
    attachment_file_name = "daily_report.txt"
    # Create a dummy file for attachment if it doesn't exist.
    # This is useful for testing the script without needing to manually create a file.
    if not os.path.exists(attachment_file_name):
        with open(attachment_file_name, "w") as f:
            f.write("This is a dummy daily report generated by Python.\n")
            f.write("Current timestamp: " + os.popen('date').read().strip()) # Adds current date/time

    attachment_path = attachment_file_name # Make sure this file exists in the same directory, or provide a full path.

    # 3. Create the email message with the attachment
    message = create_message_with_attachment(
        sender_email, 
        recipient_email, 
        email_subject, 
        email_body, 
        attachment_path
    )

    # 4. Send the email using the authenticated service
    if message:
        send_message(service, sender_email, message)
        print("Email sent successfully!")
    else:
        print("Failed to create email message. Check file paths and content.")

if __name__ == '__main__':
    main()

Complete Code

Here’s the full script for your convenience. Remember to replace your-email@example.com with the actual email address you want to send the email to!

import os
import pickle
import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

def authenticate_gmail():
    """Shows user how to authenticate with Gmail API and stores token.
    The file token.pickle stores the user's access and refresh tokens, and is
    created automatically when the authorization flow completes for the first
    time.
    """
    creds = None
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('gmail', 'v1', credentials=creds)
    return service

def create_message_with_attachment(sender, to, subject, message_text, file_path):
    """Create a message for an email with an attachment."""
    message = MIMEMultipart()
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject

    msg = MIMEText(message_text)
    message.attach(msg)

    try:
        with open(file_path, 'rb') as f:
            part = MIMEBase('application', 'octet-stream')
            part.set_payload(f.read())
        encoders.encode_base64(part)

        file_name = os.path.basename(file_path)
        part.add_header('Content-Disposition', 'attachment', filename=file_name)
        message.attach(part)
    except FileNotFoundError:
        print(f"Error: Attachment file not found at '{file_path}'. Sending email without attachment.")
        pass

    raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
    return {'raw': raw_message}

def send_message(service, user_id, message):
    """Send an email message.

    Args:
        service: Authorized Gmail API service instance.
        user_id: User's email address. The special value "me" can be used to indicate the authenticated user.
        message: A dictionary containing the message to be sent.

    Returns:
        The sent message object if successful, None otherwise.
    """
    try:
        sent_message = service.users().messages().send(userId=user_id, body=message).execute()
        print(f"Message Id: {sent_message['id']}")
        return sent_message
    except HttpError as error:
        print(f"An error occurred while sending the email: {error}")
        return None

def main():
    service = authenticate_gmail()

    sender_email = "me"
    recipient_email = "your-email@example.com" # !!! IMPORTANT: CHANGE THIS TO YOUR ACTUAL RECIPIENT'S EMAIL ADDRESS !!!
    email_subject = "Automated Daily Report - From Python!"
    email_body = (
        "Hello Team,\n\n"
        "Please find the attached daily report for your review. This email "
        "was automatically generated by our Python script.\n\n"
        "Best regards,\n"
        "Your Friendly Automation Bot"
    )

    attachment_file_name = "daily_report.txt"
    if not os.path.exists(attachment_file_name):
        with open(attachment_file_name, "w") as f:
            f.write("This is a dummy daily report generated by Python.\n")
            f.write("Current timestamp: " + os.popen('date').read().strip())

    attachment_path = attachment_file_name

    message = create_message_with_attachment(
        sender_email, 
        recipient_email, 
        email_subject, 
        email_body, 
        attachment_path
    )

    if message:
        send_message(service, sender_email, message)
        print("Email sent successfully!")
    else:
        print("Failed to create email message. Check file paths and content.")

if __name__ == '__main__':
    main()

How to Run Your Script

  1. Save the Code: Save the Python code above as send_gmail_attachment.py (or any other .py name you prefer) in the same directory where you saved your credentials.json file.
  2. Create an Attachment (Optional): Ensure the file specified in attachment_path (e.g., daily_report.txt) exists in the same directory. The script will create a dummy one if it’s missing, but you can replace it with any real file you wish to send.
  3. Update Recipient Email: Crucially, change recipient_email = "your-email@example.com" in the main() function to the actual email address you want to send the email to. You can send it to yourself for testing!
  4. Run from Terminal: Open your terminal or command prompt, navigate to the directory where you saved your files, and run the script using the Python interpreter:
    bash
    python send_gmail_attachment.py
  5. First Run Authentication: The very first time you run the script, a web browser window will automatically open. It will ask you to log in to your Google account and grant permissions to your “Python Gmail Sender” application. Follow the prompts, allow access, and you’ll typically be redirected to a local server address. Once granted, the script will save your token.pickle file and proceed to send the email.
  6. Subsequent Runs: For all future runs, as long as the token.pickle file is valid, the script will send the email without needing to re-authenticate via the browser, making your automation truly seamless.

Troubleshooting Tips

  • FileNotFoundError: [Errno 2] No such file or directory: 'credentials.json': This means your Python script can’t find the credentials.json file. Make sure it’s saved in the same folder as your Python script, or provide the full, correct path to the file.
  • Browser Not Opening / oauthlib.oauth2.rfc6749.errors.InvalidGrantError: This often indicates an issue with your credentials.json file or how your Google Cloud Project is set up.
    • Double-check that you selected “Desktop app” for the OAuth Client ID type.
    • Ensure the Gmail API is enabled for your project.
    • Verify that your email address is added as a “Test user” on the OAuth Consent Screen.
    • If you’ve made changes, it’s best to delete token.pickle and download a new credentials.json file, then try running the script again.
  • “Error: Attachment file not found…”: This message will appear if the file specified in attachment_path does not exist where the script is looking for it. Make sure the file (daily_report.txt in our example) is present, or update attachment_path to the correct full path to your attachment.
  • “An error occurred while sending the email: : A 403 error typically means “Forbidden,” which suggests an authorization problem. Delete token.pickle and credentials.json, then restart the setup process from “Step 1: Setting Up Your Google Cloud Project” to ensure all permissions are correctly granted.

Conclusion

Congratulations! You’ve just built a powerful Python script to automate sending emails with attachments using the Gmail API. This is just the beginning of what you can achieve with automation. Imagine integrating this with other scripts that generate financial reports, process website data, or monitor server events – the possibilities are endless for making your digital life more efficient.

Keep experimenting, modify the email content, try different attachments, and explore how you can integrate this into your daily workflow. Happy automating!

Comments

Leave a Reply