How I Automated Certificate Generation with Python After a Series of Hiccups

The past two days have been quite challenging.

After hours of effort yesterday, I began to doubt whether I could complete the task assigned to me.

It seemed like there was no way out.

The task seemed simple at first: I had a Google Sheet with 158 participants, a certificate in the form of a Word file, and I needed to send emails using Outlook.

Initial Setup and Challenges with Outlook

Sounds easy, right?

I spent almost 2-3 hours trying to configure Outlook using my university’s email ID.

Unfortunately, it didn’t work out due to some barriers, possibly related to app password generation, but I wasn’t sure why.

Later, I tried the setup on my own PC, only to discover that Word and Outlook are paid applications for Windows 11.

I had to complete the task by Monday, and yesterday was Saturday. So, I needed to find an alternative.

I experimented with Google Sheets’ Apps Script, but ran into another issue:

“being a Google product, it didn’t work well with Word. The certificates were generated, but all showed <<Full Name>> instead of the actual names.”

Exploring Alternative Solutions: Python and Google Sheets

I then tried using Python, integrating several Google Cloud APIs, only to realize that I was essentially doing the same thing but in a different way.

Frustrated, I decided to call it a day and went to sleep.

Today, I had to prepare for my exams, so I focused on that first.

A Fresh Start: Tackling the Problem with Python

After I was done studying, I returned to the task with a fresh mind.

I realized that Python was still my best option, so I continued working with various word-related modules.

Unfortunately, none of them allowed me to modify the Word file due to its complex design.

Then, I remembered that I had the certificate template in image format as well. This seemed like a promising solution.

Generating Certificates with Python

I extracted the participants’ names from the Google Sheet and used the following Python code to generate the certificates:

Script 👇 (Click for Code)
from PIL import Image, ImageDraw, ImageFont
import os

# Load the certificate template
template_path = 'certificate_template.jpg'
template = Image.open(template_path)

# Set up font and text position
font_path = 'font file path/arial.ttf'  # Path to the font file
font_size = 50  # Adjust the size as needed
font = ImageFont.truetype(font_path, font_size)
text_color = (50, 50, 200)  # Color of the text

# Coordinates for the text (adjust these based on your template)
name_x, name_y = 200, 320

# Load participant names
# All The participant names were in this list.
# Can easily generate using pandas
participants = ["ZZZ", "XXX", "YYY"]

# Output directory
output_dir = "certificates"
os.makedirs(output_dir, exist_ok=True)

# Generate certificates
for name in participants:
    # Make a copy of the template
    cert = template.copy()
    draw = ImageDraw.Draw(cert)

    # Add the participant's name
    draw.text((name_x, name_y), name, fill=text_color, font=font)

    # Save the certificate
    output_path = os.path.join(output_dir, f"{name}.jpg")
    cert.save(output_path)
    print(f"Certificate generated for: {name}")

print("All certificates have been generated.")

Then I uploaded these certificates to the Google Drive folder.

And finally, went onto mail them.

This app script was used for that purpose.

Script 👇 (Click for Code)
function sendCertificates() {
  // Spreadsheet information
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1"); 
  var data = sheet.getDataRange().getValues();
  
  // Google Drive folder ID where certificates are stored
  var folderId = "folder_id"; // Replace with your actual folder ID
  var folder = DriveApp.getFolderById(folderId);

  // Iterate over rows in the sheet
  for (var i = 1; i < data.length; i++) { // Start at 1 to skip the header row
    var email = data[i][1]; // B column - Email Address
    var name = data[i][2]; // C column - Full Name
    var subject = "Participation Certificate";
    var body = "Hello " + name + ",\n\n" +
               "Congratulations on successfully <<complete mail..>>";

    // Search for the certificate file by name within the folder
    var files = folder.getFilesByName(name + ".jpg"); // Adjust extension if needed
    
    if (files.hasNext()) {
      var file = files.next();
      MailApp.sendEmail({
        to: email,
        subject: subject,
        body: body,
        attachments: [file.getAs(MimeType.JPEG)] // Adjust MimeType if using a different file format
      });
      Logger.log("Email sent to: " + email);
    } else {
      Logger.log("Certificate not found for: " + name);
    }
  }
}

And due to some naming inconsistencies only 73 out of the total mails were sent.

Finalizing the Task: Overcoming Naming Inconsistencies

My Failed Efforts. 👇 (Click for Code)
function deleteRowsBasedOnNames() {
  // The list of names you want to keep
  const namesToKeep = [];

  // Convert namesToKeep to lowercase for case-insensitive comparison
  const namesToKeepLower = namesToKeep.map(name => name.toLowerCase());
 
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Copy");
  const range = sheet.getRange("C:C"); // Assuming names are in column C
  const values = range.getValues();
  
  let rowsDeleted = 0;

  // Iterate over the rows and delete rows not in the list
  for (let i = values.length - 1; i >= 0; i--) {
    if (values[i][0]) {
      const nameLower = values[i][0].toLowerCase();
      if (!namesToKeepLower.includes(nameLower)) {
        sheet.deleteRow(i + 1);
        rowsDeleted++;
      }
    }
  }

  // Log the number of rows deleted
  Logger.log(`Total rows deleted: ${rowsDeleted}`);
}

This removed the entire sheet.

Then I handpicked the ones left after a lot of programmatical errors. (I tried almost 10 iterations on the above code)

Finally using this script I mailed the rest handpicked ones.

Script 👇 (Click for Code)
function sendCertificates() {
  // Spreadsheet information
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2"); 
  var data = sheet.getDataRange().getValues();
  
  // Google Drive folder ID where certificates are stored
  var folderId = "folder_id"; // Replace with your actual folder ID
  var folder = DriveApp.getFolderById(folderId);

  // Iterate over rows in the sheet
  for (var i = 1; i < data.length; i++) { // Start at 1 to skip the header row
    var email = data[i][1]; // B column - Email Address
    var name = data[i][2].trim().replace(/\s+/g, ' '); // C column - Full Name (trimmed and spaces normalized)
    var subject = "Participation Certificate ";
    var body = "Hello " + name + ",\n\n" +
               "Congratulations on successfully <<complete mail..>>";

    // Search for the certificate file by name within the folder
    var files = folder.getFiles();
    var fileFound = false;

    while (files.hasNext()) {
      var file = files.next();
      var fileName = file.getName().trim().replace(/\s+/g, ' ').toLowerCase();
      
      if (fileName === name.toLowerCase() + ".jpg") { // Adjust extension if needed
        MailApp.sendEmail({
          to: email,
          subject: subject,
          body: body,
          attachments: [file.getAs(MimeType.JPEG)] // Adjust MimeType if using a different file format
        });
        Logger.log("Email sent to: " + email);
        fileFound = true;
        break;
      }
    }

    if (!fileFound) {
      Logger.log("Certificate not found for: " + name);
    }
  }
}

Lessons Learned: The Importance of Flexibility in Programming

And, the Task is successfully completed.

I learned a lot along the way, especially that no single tool, technology, or language is ever enough. As a programmer, you need to be flexible and adapt to different tools and techniques.

It was a fun and productive weekend for me. How was yours?

Leave a Reply