Skip to content

xavier-oc-programming/mail-merge-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mail Merge

A Python script that generates personalized letters by replacing a [name] placeholder in a template for each recipient in a list.

Table of Contents


Quick start

git clone https://github.com/xavier-oc-programming/mail-merge-python
cd mail-merge-python
python menu.py

Select 1 for the original course build or 2 for the advanced OOP build.

To run a build directly:

python original/main.py
python advanced/main.py

Builds comparison

Feature Original Advanced
Reads template file
Reads names list
Replaces [name] placeholder
Generates one file per name
Skips blank lines in names file
Path-based portable file paths
MailMerger class (OOP)
Constants centralized in config.py
Pure-logic generate() (no file I/O)
Completion summary with letter count

Usage

No arguments or environment variables required. Both builds run silently and print a completion message on finish.

Mail merge complete! 8 letters saved to advanced/output/

Data flow

Input: starting_letter.txt      Input: invited_names.txt
       (template with                  (one name per line)
        [name] placeholder)
              |                               |
              +---------------+---------------+
                              |
                              v
                   For each name:
                   - strip whitespace
                   - replace [name] in template
                   - write letter_for_{name}
                              |
                              v
                   Print completion summary

At each stage:

  • Load: str (raw template text) and list[str] (name strings)
  • Process: one str per recipient after replace() and strip()
  • Output: plain-text files named letter_for_{name} in the output directory

Features

Template substitution — A single [name] token in the template is replaced with each recipient's name, producing one personalized letter per person.

Name list reading — The names file is read line by line. The advanced build additionally strips whitespace and skips blank lines, making it robust to trailing newlines or empty lines in the names file.

File output — Each letter is written to advanced/output/ (or original/Output/ReadyToSend/) with the filename letter_for_{name}. Generated files are git-ignored; example.txt is committed to document the expected format.

Completion summary (advanced only) — Prints the count of letters generated and the output path.

Pure-logic generate() method (advanced only) — Accepts a template string and a name, returns the personalized letter string. No file I/O, no side effects — independently testable without touching the filesystem.

Centralized constants (advanced only) — All file paths and string tokens are defined once in config.py. Changing the template location, output directory, or placeholder token requires editing one file.


Navigation flow

Terminal menu

python menu.py
       |
       v
 +---------------------------+
 |   MAIL MERGE  (menu)      |<-----------------------+
 +---------------------------+                        |
 | 1 -> original build       |                        |
 | 2 -> advanced build       |                        |
 | q -> quit                 |                        |
 +-------------+-------------+                        |
               |                                      |
     +---------+----------+----------+                |
     |         |                     |                |
     v         v                     v                |
    "1"       "2"              other input            |
     |         |                     |                |
     v         v                     v                |
 original/  advanced/        "Invalid choice.         |
 main.py    main.py           Try again."             |
     |         |                     |                |
     +---------+                     +----------------+
          |
          v
   script completes
   "Press Enter to return..."
          |
          +--------------------------------------------->(loop)

Execution flow

+------------------------------+
|  Load config                 |  TEMPLATE_PATH, NAMES_PATH, OUTPUT_DIR
+---------------+--------------+
                |
                v
+------------------------------+
|  Read template               |  starting_letter.txt -> str
+---------------+--------------+
                |
                v
+------------------------------+
|  Read names                  |  invited_names.txt -> list[str]
+---------------+--------------+
                |
                v
+------------------------------+
|  For each name:              |
|    strip whitespace          |
|    replace [name] in         |
|    template                  |
|    write letter_for_{name}   |
+---------------+--------------+
                |
                v
+------------------------------+
|  Print completion summary    |
+------------------------------+

Architecture

mail-merge-python/
|-- menu.py              # CLI launcher -- prompts for build selection
|-- art.py               # LOGO constant displayed by menu.py
|-- requirements.txt     # stdlib only; no pip install required
|-- .gitignore
|-- README.md
|-- docs/
|   `-- COURSE_NOTES.md  # original course exercise description
|-- original/            # verbatim course solution (branch: original)
|   |-- main.py          # procedural; only change: Path-based file paths
|   |-- Input/
|   |   |-- Letters/
|   |   |   `-- starting_letter.txt
|   |   `-- Names/
|   |       `-- invited_names.txt
|   `-- Output/
|       `-- ReadyToSend/ # generated letters (git-ignored); example.txt committed
`-- advanced/            # OOP refactor
    |-- config.py        # all constants -- paths, placeholder, prefix
    |-- merger.py        # class MailMerger -- pure logic, no I/O side effects
    |-- main.py          # orchestrator -- wires config, MailMerger, and file output
    |-- input/
    |   |-- Letters/
    |   |   `-- starting_letter.txt
    |   `-- Names/
    |       `-- invited_names.txt
    `-- output/          # generated letters (git-ignored); example.txt committed
        `-- example.txt

Module reference

merger.py — class MailMerger

Method Returns Description
__init__(template_path, names_path) Stores Path objects for the template and names files
load_template() str Reads and returns the full template text
load_names() list[str] Reads names file; strips whitespace and drops blank lines
generate(template, name) str Replaces NAME_PLACEHOLDER with name in the template string

Configuration reference

Constant Default Description
BASE_DIR Path(__file__).parent Root of the advanced/ directory
TEMPLATE_PATH BASE_DIR / "input/Letters/starting_letter.txt" Letter template with [name] placeholder
NAMES_PATH BASE_DIR / "input/Names/invited_names.txt" Recipient names, one per line
OUTPUT_DIR BASE_DIR / "output" Directory where personalized letters are written
NAME_PLACEHOLDER "[name]" Token in the template replaced per recipient
OUTPUT_FILE_PREFIX "letter_for_" Prepended to each name to form the output filename

Data schema

Input — starting_letter.txt

Plain text. Contains exactly one [name] token.

Dear [name],

You are invited to my birthday this Saturday.

Hope you can make it!

Angela

Input — invited_names.txt

Plain text. One name per line. Blank lines are ignored by the advanced build.

Aang
Zuko
Appa
Katara
Sokka
Momo
Uncle Iroh
Toph

Output — letter_for_{name}

Plain text. One file per recipient. Identical to the template with [name] replaced.

Dear Aang,

You are invited to my birthday this Saturday.

Hope you can make it!

Angela

Design decisions

config.py centralizes all paths and tokens — changing the input folder, output folder, or placeholder token requires editing one file. No magic strings scattered across modules.

MailMerger.generate() is pure logic — it accepts a template string and a name, returns a string. No file I/O, no side effects. This means it can be tested without touching the filesystem: pass any string in, assert the output.

load_names() strips and filtersstrip() removes leading/trailing whitespace and newlines. Blank lines are dropped. This makes the script robust to editors that append a trailing newline to the names file.

Path(__file__).parent for all file paths — all paths are resolved relative to the script's own location, not the working directory. Both python original/main.py and menu.py → 1 work correctly regardless of where the interpreter is invoked from.

Pure-logic modules raise exceptions, not sys.exit()MailMerger raises FileNotFoundError if a path is missing. main.py decides how to handle it. This keeps modules reusable and independently testable.

sys.path.insert in advanced/main.py — inserting Path(__file__).parent at the front of sys.path ensures sibling imports (from config import ..., from merger import ...) resolve correctly whether launched via menu.py or run directly.

subprocess.run with cwd=path.parent in menu.py — sets the working directory to the build's own folder before launching. This ensures any relative-path code in the build resolves correctly from the right location.

while True in menu.py, not recursion — after a build finishes, control returns to the loop naturally. Recursion would grow the call stack on every build run; a loop does not.

Console cleared before every menu renderos.system("clear") at the top of each loop iteration keeps the menu clean after a build completes and prints its output.


Course context

Built as Day 24 of 100 Days of Code: The Complete Python Pro Bootcamp by Dr. Angela Yu.

Concepts covered in the original build: file I/O (open, read, readlines, write), string manipulation (replace, strip), f-strings, for loops over file lines.

The advanced build extends into: OOP (MailMerger class), separation of concerns (config / logic / orchestration), pathlib.Path, pure-function design.

See docs/COURSE_NOTES.md for the full original course exercise description.


Dependencies

Module Used in Purpose
os menu.py Clear the console with os.system
sys menu.py, advanced/main.py sys.executable, sys.path.insert
subprocess menu.py Launch builds in a child process
pathlib.Path advanced/config.py, advanced/main.py, original/main.py Portable, manipulation-friendly file paths

About

Day 24 of 100 Days of Code — Python mail merge script that generates personalized letters from a template

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages