47 Automations · A public portfolio by Hamras

Automations I built for my own work.

Four agents that take the repetitive part of my week: finding who to write to, applying for jobs, clearing three inboxes and sending my morning brief. I designed and built each one around the same rule. Software does the routine work, and anything that goes out in my name waits for me.

My automationsrun loops4 agents
  • 01Cmail AgentCMail Scraperon request
    1. Find
    2. Verify
    3. Sort
    4. Draft
    5. Send
  • 02Job Application Agentjob-hunter + autoapplysupervised
    1. Collect
    2. Score
    3. Shortlist
    4. Fill
    5. Check
    6. Submit
  • 03Inbox Triage AgentTARS Mailerevery 60s
    1. Check
    2. Post
    3. Choose
    4. Draft
    5. Send
  • 04Daily Brief AgentTARS Brief06:30 daily
    1. Schedule
    2. Gather
    3. Compose
    4. Send
    5. Track
Python · Supabase · Telegramme approving what goes out

The automations

Four agents,
one job each.

Each one started as a chore I was tired of doing by hand. Here is what each does, why I built it, and the decisions that shaped it.

01

Cmail Agent

CMail Scraper
Outreach

Finds the right people to write to, and shows where it found them.

Why I built it. I was sending my CV to companies in Qatar and spent longer hunting for a real HR address than writing the email. Guessed addresses bounce, and directory sites recycle stale ones.

How it works. I name a company or a sector. The agent searches the web and reads each company’s own careers and contact pages, while a small Python tool crawls the site politely, stores every address with the page it came from, checks the domain accepts mail, and exports a filterable Excel tracker. When I choose who to contact, it drafts the email and I send it.

A 20-second film of Cmail Agent finding company contacts, sorting them into a spreadsheet, drafting an email and sending it after approval.
  • Never guesses an addressOnly addresses that appear on a real page are stored, each with its source URL. No hr@ patterns.
  • Directories give names, not leadsListing sites are used to find companies. Their own addresses are flagged low confidence and never queued.
  • One tracker, no repeatsSQLite underneath, Excel on top. Every address has a status, so no company hears from me twice.
Run loop
  1. Find
  2. Verify
  3. Sort
  4. Draft
  5. Send

Where I decideWho to target, and every email. It only searches when I ask, and nothing leaves my outbox until I send it.

A decision I made

I folded my older lead scraper into it instead of starting clean. Its data came across cleaned and de-duplicated, and the companies I had already written to kept their sent dates.

Built with
  • Python
  • SQLite
  • openpyxl
  • MX checks
  • pytest
02

Job Application Agent

job-hunter + autoapply
Careers

Finds the roles worth applying for, then fills the forms with facts I have checked.

Why I built it. Applying across Qatar’s job boards meant typing the same answers into dozens of forms, and the roles that fit were buried under ones I was never going to get.

How it works. Two halves. The hunter collects postings from Indeed and LinkedIn across several search terms and scores each one from 0 to 100 against my CV with a written rubric covering skills, domain, seniority, location and freshness. It quotes the advert and names the gaps. The applier takes the shortlist, opens each posting in a real browser and fills the form only from a facts file I keep.

  • Facts, not guessesOnly values from my facts file are entered. An unanswered item in that file stops the run.
  • Asks once, remembersA required question it cannot answer comes to me on Telegram. My answer is stored and reused next time.
  • Never applies twiceIts intent is written to a SQLite ledger before a form is touched, with a cap of five applications a day.
Run loop
  1. Collect
  2. Score
  3. Shortlist
  4. Fill
  5. Check
  6. Submit

Where I decideReview mode is the default: it fills the form, reads every value back off the page, and stops for me to press submit.

A decision I made

Indeed blocks headless browsers. Instead of adding stealth patches, I made it run a visible browser with a kept profile. Sign-in walls, CAPTCHAs and assessments stop the run; it never tries to get past them.

Built with
  • Python
  • Playwright
  • SQLite
  • Telegram
  • pytest
03

Inbox Triage Agent

TARS Mailer
Email

Three inboxes, one chat, and nothing sent without a tap.

Why I built it. I check two Gmail accounts and an Outlook account. Most mail needs one of two things: deleting, or a one-line answer. Opening three apps to do that was the slow part.

How it works. A small Python service checks each mailbox every minute, over IMAP for Gmail and Microsoft Graph for Outlook, and posts every new email to my private Telegram chat with Reply, Delete and Open buttons. To reply, I type what I want to say in a line. One short call to a small language model turns that into a draft, with Send, Redo and Cancel.

  • Free while idleChecking and posting are plain code. The language model runs only when I ask for a draft.
  • Picks up where it left offEach account keeps its own cursor, so mail that arrived while the laptop was off is posted on the next start.
  • Delete means TrashDelete moves mail to Trash or Deleted Items. Nothing is removed for good from the chat.
Run loop
  1. Check
  2. Post
  3. Choose
  4. Draft
  5. Send

Where I decideEvery reply. A draft goes out only when I tap Send, and the bot ignores every chat except mine.

A decision I made

No filtering, on purpose. Every email arrives, because deciding from one place is the whole point. It starts with Windows from a tray icon with a watchdog that restarts it if it stops.

Built with
  • Python standard library
  • IMAP / SMTP
  • Microsoft Graph
  • Telegram Bot API
  • SQLite
04

Daily Brief Agent

TARS Brief
Daily brief

My morning in one message, with no language model involved.

Why I built it. I was opening five apps before breakfast for the weather, prayer times, the news and something worth reading. I wanted one message, the same shape every day.

How it works. Scheduled Supabase Edge Functions. At 6:30 Doha time it sends the brief: weather, the day’s prayer times, a verse and a hadith, then the news as pages (AI models, top stories, tech) that I flip with arrow buttons. Through the day it pings at each prayer time and keeps a streak when I tap Prayed. Twice a day it also sends an idea from a book and a couplet of classic Urdu poetry.

  • Deterministic by designEvery message is built from public APIs and stored content. It costs nothing per run and cannot make things up.
  • Buttons that feel instantThe webhook answers a tap inside the same HTTP response, and a ping every minute keeps the function warm.
  • Secrets in the vaultBot tokens and webhook secrets live in the database vault, not in code or environment files.
Run loop
  1. Schedule
  2. Gather
  3. Compose
  4. Send
  5. Track

Where I decideNothing to approve: it only ever writes to me. An untapped prayer ping is deleted when the next one arrives, so the chat stays clean.

A decision I made

I built a section showing my API usage, then removed it once I saw I never read it. A brief earns its place by staying short.

Built with
  • Supabase Edge Functions
  • TypeScript
  • Postgres + pg_cron
  • Supabase Vault
  • Telegram Bot API

How I build them

It does the work.
I stay in control.

Every automation here asks itself one question before it acts. The answer decides whether it carries on or waits for me.

Could this step be undone easily?

YesIt runs

Searching, crawling, scoring, sorting and drafting are easy to reverse, so they run without waiting for me.

NoIt prepares, then asks me

Sending an email or submitting an application cannot be taken back, so it gets everything ready and waits for my approval.

  • Code first, a model only for languageChecking mail, crawling, scoring and scheduling are plain code. A language model reads pages and drafts text, and nothing else.
  • Ask, never guessWhen the job agent meets a question it cannot answer from my facts file, it stops and asks me on Telegram instead of filling something in.
  • Write it down firstThe job agent records what it is about to do before it touches a form. The contact finder logs every search and the page each address came from.
  • Nothing in my name without meEmails and applications are prepared, then wait for my tap. Review mode is the default, not an option.
  • Easy to undoDelete moves mail to Trash. A submit with no confirmation is recorded as unknown and never retried on its own.
  • Tested offlineThe contact finder and the job agent each have a pytest suite. The job agent’s runs against mock pages, never live job sites.

Contact

Want to see one running?

I am happy to walk through any of these: how it works, the decisions behind it, and what broke along the way.