From d3491b8a4aeba174a28aab1634d5588efce36d4d Mon Sep 17 00:00:00 2001 From: dheerajgajula02 Date: Mon, 19 Jan 2026 02:20:50 -0700 Subject: [PATCH] initial commit --- .dockerignore | 9 ++++++ .env | 3 ++ .gitignore | 2 ++ Dockerfile | 20 ++++++++++++ README.md | 71 +++++++++++++++++++++++++++++++++++++++++ _mon_tech_stack.txt | 75 +++++++++++++++++++++++++++++++++++++++++++ email_sender.py | 66 ++++++++++++++++++++++++++++++++++++++ main.py | 42 ++++++++++++++++++++++++ monitoring_stack.txt | 76 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 ++ test.py | 30 +++++++++++++++++ 11 files changed, 397 insertions(+) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 _mon_tech_stack.txt create mode 100644 email_sender.py create mode 100644 main.py create mode 100644 monitoring_stack.txt create mode 100644 requirements.txt create mode 100644 test.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d4243c9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +__pycache__ +*.pyc +venv +.venv +.env +.DS_Store +dist +build +\.pytest_cache diff --git a/.env b/.env new file mode 100644 index 0000000..10b48f3 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +email="contact.dheerajgajula@gmail.com" +apppassword="astkbadcyjwbwkrq" +receipt_email="dheerajgajula.cse@gmail.com" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93526df --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv/ +__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f849e71 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.11-slim + +# Set a working directory +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Environment variables (provided as requested) +ENV email=contact.dheerajgajula@gmail.com +ENV apppassword=astkbadcyjwbwkrq +ENV receipt_email=dheerajgajula.cse@gmail.com + +# Expose port 9999 and run the FastAPI app with uvicorn +EXPOSE 9999 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9999"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdaadd2 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Email Sender Service + +This repository contains a simple `EmailSender` class and a small FastAPI service that exposes an endpoint to send emails from the configured sender to the configured recipient. + +Required environment variables (e.g. in a `.env` file): + +- `email` - sender email address (example: your Gmail address) +- `apppassword` - SMTP app password (for Gmail, generate an app password) +- `receipt_email` - recipient email address (where messages will be sent) + +Optional environment variables for test script: + +- `TEST_SUBJECT` - subject used by `test.py` if provided +- `TEST_CONTENT` - content used by `test.py` if provided + +Install dependencies + +It's recommended to use a virtual environment. Then: + +```bash +pip install -r requirements.txt +``` + +Run the FastAPI service locally with uvicorn: + +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +Request example (curl): + +```bash +curl -X POST "http://localhost:8000/send" -H "Content-Type: application/json" -d '{"subject":"Hello","content":"This is a test"}' +``` + +Notes + +- The service uses Gmail's SMTP by default (the existing `EmailSender` implementation). If you want to use a different SMTP provider, update `_send_email` in `email_sender.py`. +- For local development without sending real email, consider running a debug SMTP server or modifying `_send_email` to print the message when a `DRY_RUN` env var is set. + +Docker +------ + +A simple Dockerfile is provided to containerize the service. It embeds the three environment values you requested directly into the image (`email`, `apppassword`, `receipt_email`). Embedding secrets in a Dockerfile is not generally recommended for production — see the security note below. + +Build the image: + +```bash +docker build -t email-service:latest . +``` + +Run the container (maps container port 9999 to host port 9999): + +```bash +docker run -p 9999:9999 --rm email-service:latest +``` + +Then POST to the `/send` endpoint: + +```bash +curl -X POST "http://localhost:9999/send" -H "Content-Type: application/json" -d '{"subject":"Hello","content":"This is a test"}' +``` + +Security note +------------- + +Storing secrets (email and app passwords) in a Dockerfile is insecure because the resulting image and layers can be inspected and shared. Safer alternatives: + +- Use an external environment file (`--env-file .env`) or pass `-e` flags to `docker run` to inject secrets at runtime (do not commit `.env` to source control). +- Use Docker secrets or a secret manager when deploying to orchestration platforms. + diff --git a/_mon_tech_stack.txt b/_mon_tech_stack.txt new file mode 100644 index 0000000..61090da --- /dev/null +++ b/_mon_tech_stack.txt @@ -0,0 +1,75 @@ +endpoints: + - name: resume + group: core + url: "https://resume.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[RESPONSE_TIME] < 150" + + - name: git + group: core + url: "https://git.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + + - name: docs + group: core + url: "https://docs.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + - name: photos backup + group: core + url: "https://pic.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + + - name: boulder-server-ping + group: server-stats + url: "icmp://10.0.0.10" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: intel-nuc + group: server-stats + url: "icmp://10.0.0.20" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: proxmox + group: server-stats + url: "icmp://10.0.0.30" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: check-domain-expiration + url: "https://dheerajg.me" + interval: 1h + conditions: + - "[DOMAIN_EXPIRATION] > 720h" + + + - name: example-dns-query + url: "8.8.8.8" # Address of the DNS server to use + interval: 10s + dns: + query-name: "example.com" + query-type: "A" + conditions: + - "[BODY] == pat(*.*.*.*)" # Matches any IPv4 address + - "[DNS_RCODE] == NOERROR" \ No newline at end of file diff --git a/email_sender.py b/email_sender.py new file mode 100644 index 0000000..9f26550 --- /dev/null +++ b/email_sender.py @@ -0,0 +1,66 @@ +import smtplib +from email.mime.text import MIMEText +from dotenv import load_dotenv +import os +load_dotenv() + +class EmailSender: + def __init__(self): + self.email_id = os.getenv("email") + self.apppassword = os.getenv("apppassword") + self.receipt_email_id = os.getenv("receipt_email") + + def message_prep(self, subject:str, email: str, body:str): + + msg = MIMEText(body) + msg["Subject"] = subject + msg["From"] = self.email_id + msg['To'] = email + + return msg + pass + + def _send_email(self, msg:MIMEText, recipient:str): + with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp_server: + # use the configured sender id and app password + smtp_server.login(self.email_id, self.apppassword) + smtp_server.sendmail(self.email_id, recipient, msg.as_string()) + pass + + def send_email(self, reason:str, email:str, body:str): + + if reason == "Job Opportunity": + subject = "Your time has comeee !!!! get up !!!!" + elif reason == "Collaboration": + subject = "Time to build somthing !! getup !" + elif reason == "General": + subject = "Someone is saying hii !!" + else: + subject = "hmmm !! what could it be :)" + # send email to self + reciept_email = self.message_prep(subject=subject, email=self.receipt_email_id, body=body + f"\n \n {email} has contacted you ") + self._send_email(msg=reciept_email, recipient=self.receipt_email_id) + # send email to the contact person + contact_person_email = self.message_prep(subject=os.getenv("contact_subject"), email=email, body=os.getenv("contact_body")) + self._send_email(msg=contact_person_email, recipient=email) + pass + + def send_general_email(self, subject:str, email:str, body:str): + # send email to self + reciept_email = self.message_prep(subject=subject, email=self.receipt_email_id, body=body + f"\n \n {email} has contacted you ") + self._send_email(msg=reciept_email, recipient=self.receipt_email_id) + # send email to the contact person + contact_person_email = self.message_prep(subject=os.getenv("contact_subject"), email=email, body=os.getenv("contact_body")) + self._send_email(msg=contact_person_email, recipient=email) + + + pass + + def send_to_recipient(self, subject: str, content: str): + """Send a simple email with given subject and content from self.email_id to self.receipt_email_id.""" + if not self.email_id or not self.apppassword or not self.receipt_email_id: + raise ValueError("Missing email configuration (email, apppassword, or receipt_email).") + + msg = self.message_prep(subject=subject, email=self.receipt_email_id, body=content) + self._send_email(msg=msg, recipient=self.receipt_email_id) + return True \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..7d3252b --- /dev/null +++ b/main.py @@ -0,0 +1,42 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +import os + +from email_sender import EmailSender + +app = FastAPI(title="Email Sender Service") + +class SendRequest(BaseModel): + subject: str + content: str + +# create a single EmailSender instance for the app +es = EmailSender() + +@app.get("/health") +def health(): + return {"status": "ok"} + +@app.post("/send") +def send_email(req: SendRequest): + """Send an email from configured sender to configured recipient. + + Expects JSON: {"subject": "...", "content": "..."} + """ + # basic config validation + missing = [] + if not es.email_id: + missing.append("email") + if not es.apppassword: + missing.append("apppassword") + if not es.receipt_email_id: + missing.append("receipt_email") + if missing: + raise HTTPException(status_code=500, detail={"error": "missing_config", "missing": missing}) + + try: + es.send_to_recipient(subject=req.subject, content=req.content) + return {"status": "sent", "to": es.receipt_email_id} + except Exception as e: + # return the error message but avoid leaking sensitive info + raise HTTPException(status_code=500, detail={"error": "send_failed", "message": str(e)}) diff --git a/monitoring_stack.txt b/monitoring_stack.txt new file mode 100644 index 0000000..c90c8a6 --- /dev/null +++ b/monitoring_stack.txt @@ -0,0 +1,76 @@ + +endpoints: + - name: resume + group: core + url: "https://resume.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[RESPONSE_TIME] < 150" + + - name: git + group: core + url: "https://git.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + + - name: docs + group: core + url: "https://docs.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + - name: photos backup + group: core + url: "https://pic.dheerajg.me" + interval: 10s + conditions: + - "[STATUS] == 200" + - "[BODY].status == UP" + - "[RESPONSE_TIME] < 150" + + + - name: boulder-server-ping + group: server-stats + url: "icmp://10.0.0.10" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: intel-nuc + group: server-stats + url: "icmp://10.0.0.20" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: proxmox + group: server-stats + url: "icmp://10.0.0.30" + interval: 1m + conditions: + - "[CONNECTED] == true" + + - name: check-domain-expiration + url: "https://dheerajg.me" + interval: 1h + conditions: + - "[DOMAIN_EXPIRATION] > 720h" + + + - name: example-dns-query + url: "8.8.8.8" # Address of the DNS server to use + interval: 10s + dns: + query-name: "example.com" + query-type: "A" + conditions: + - "[BODY] == pat(*.*.*.*)" # Matches any IPv4 address + - "[DNS_RCODE] == NOERROR" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b527bb6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +python-dotenv==1.1.1 +fastapi==0.100.0 +uvicorn[standard]==0.22.0 \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..f0b6343 --- /dev/null +++ b/test.py @@ -0,0 +1,30 @@ +from email_sender import EmailSender +import os + + +def main(): + """Simple test runner that sends an email using EmailSender.send_to_recipient. + + Make sure the following environment variables are set (for example in a .env file): + - email (sender email) + - apppassword (smtp/app password) + - receipt_email (recipient email) + + Run this file and let me know whether you receive the email. + """ + + subject = os.getenv('TEST_SUBJECT', 'Test email from EmailSender') + content = os.getenv('TEST_CONTENT', 'This is a test email sent by EmailSender.send_to_recipient') + + es = EmailSender() + + try: + print(f"Sending -> from: {es.email_id} to: {es.receipt_email_id} subject: {subject}") + es.send_to_recipient(subject=subject, content=content) + print("Send attempted — if SMTP credentials are correct, the recipient should receive the message.") + except Exception as e: + print("Error while sending email:", repr(e)) + + +if __name__ == '__main__': + main()