Skip to content

sayuru-akash/sms-sender-python-textware

Repository files navigation

📱 SMS Campaign Manager

Python 3.10+ Streamlit 1.55 Pytest Last Commit

Complete SMS sending solution with rate limiting, retry logic, error handling, logging, and reporting.


⚡ Quick Start

# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Launch web dashboard
python main.py --streamlit

Opens interactive dashboard at http://localhost:8501 by default.


🎯 What This Does

Sends personalized SMS messages to recipients from CSV files using the Text-Ware SMS API.

Features:

  • ✅ Flexible CSV selection (sample or uploaded)
  • ✅ Unsaved imported recipient lists can be used immediately
  • ✅ Batch cleaner for raw recipient exports in resources/input/
  • ✅ Single & bulk SMS sending
  • ✅ Automatic name personalization {name}
  • ✅ Smart name handling - limits to first 2 words
  • ✅ Automatic phone cleanup to Sri Lanka format 94XXXXXXXXX
  • ✅ Email format validation on import and manual add
  • ✅ Invalid CSV rows are rejected with row-level reasons
  • ✅ Rate limiting (adjustable) - prevents API overload
  • ✅ Automatic retry (3 attempts) - handles failures
  • ✅ Detailed logging - all events recorded
  • ✅ JSON reports - campaign results
  • ✅ Error handling - graceful failure handling
  • ✅ Web dashboard - full interactive UI
  • ✅ CLI tools - command line interface
  • ✅ Menu system - user-friendly navigation

📁 Project Structure

sms-sender-python-textware/
├── sms_sender.py          Core SMS engine
├── main.py                CLI entry point
├── streamlit_app.py       Web dashboard (recommended)
├── quickstart.py          Menu system
├── clean_recipient_batches.py  Batch CSV cleaner for raw recipient exports
├── .env                   SMS credentials (NOT committed - see .env.sample)
├── .env.sample            Environment template (reference)
├── recipients.csv         Optional saved recipient list
├── resources/
│   ├── message_template.txt   Default SMS template (editable)
│   ├── sample-recipients.csv  Bundled sample data
│   ├── input/                 Raw CSV imports to be cleaned (gitignored, `.gitkeep` kept)
│   └── output/                Cleaned CSV exports from the batch cleaner (gitignored, `.gitkeep` kept)
├── requirements.txt       Python dependencies
├── pytest.ini             Pytest configuration
├── tests/                 Automated test suite
├── README.md              This documentation
└── venv/                  Virtual environment

Auto-generated folders:
├── logs/                  Application logs with timestamps
└── reports/               JSON campaign reports

Security Note: .env file is in .gitignore and never committed. Only .env.sample is in the repo as a template.


🚀 How to Use

Web Dashboard (Recommended) ⭐

python main.py --streamlit

Opens at http://localhost:8501 with full UI.

Direct Streamlit command also works:

python -m streamlit run streamlit_app.py

Dashboard Features:

1️⃣ Recipients Tab

  • 🔄 CSV File Selection: Switch between:
    • Sample (default, backed by bundled sample data)
    • recipients.csv (saved custom list, if you choose to persist one)
    • Imported (...) (temporary in-memory upload)
  • 📊 View Recipients: Table showing all current recipients
  • ➕ Add Recipient: Single form entry for one person
  • 📤 Upload CSV: Upload custom recipients file
  • Show total recipient count

2️⃣ Campaigns Tab

  • ✏️ Message Template: Customize SMS with {name} placeholder
  • ⚙️ Campaign Settings:
    • Shows selected recipients count
    • Rate limit slider (1-10 seconds)
  • 👁️ Preview: See list of recipients who'll receive SMS
  • 🧪 Test Send: Send one test SMS to verify
  • 🚀 Send Campaign: Send to all recipients (with confirmation)
  • 📊 Results: View success/failure metrics

3️⃣ Reports Tab

  • 📋 View Reports: Select from all campaign reports
  • 📈 Summary Metrics: Total, accepted, error, and skipped counts
  • 📝 Detailed Log: Compact table for each SMS iteration
  • 📄 Full Message Drill-down: Open the full stored message body for any row
  • 📤 CSV Export: Download the current filtered report rows as CSV
  • 📥 Download: Export report as JSON

4️⃣ Settings Tab

  • View SMS API configuration (read-only for security)
  • View log and report file counts

CLI Interface

# Menu system (interactive)
python quickstart.py

# Send test SMS
python main.py --test

# Send to all recipients
python main.py --bulk

Batch Cleaner for Raw CSV Exports

Clean every pending raw CSV in resources/input/ into resources/output/:

python clean_recipient_batches.py

Clean one specific file:

python clean_recipient_batches.py --file "resources/input/your-file.csv"

Optional flags:

  • --force rebuilds an output even if a cleaned file already exists
  • --input-dir scans a different input directory
  • --output-dir writes cleaned files to a different output directory

Direct Python Usage

from sms_sender import SMSSender, get_sms_message

sender = SMSSender()
message = get_sms_message()
result = sender.send_sms("0768622302", message, "Name", "email@example.com")

📋 Recipients Management

Using Sample Recipients (Default)

  • resources/sample-recipients.csv is bundled with the app and always available through the Sample source
  • Contains: Sayuru Akash, test@gmail.com, 0777123456
  • Best option for first-time setup and quick verification

Upload Custom Recipients

  1. Go to Recipients Tab
  2. Click Upload Recipients CSV
  3. Select your CSV file
  4. Choose Use now to work from the cleaned upload immediately without writing any file
  5. Choose Save as recipients.csv only if you want the cleaned list persisted on disk

CSV Format Required

name,email,contact_number
Sayuru Akash,test@gmail.com,0777123456
John Doe,john@example.com,0761234567
Jane Smith,jane@example.com,0768765432

Requirements:

  • name: Optional, used for personalization when present (automatically limited to first 2 words)
  • email: Optional, but if provided it must be a valid format (example: info@codezela.com)
  • contact_number: Required and must be a valid Sri Lanka mobile number

Minimum supported CSV:

contact_number
0777123456
0761234567

Accepted phone input formats (all normalized to 94XXXXXXXXX):

  • 7XXXXXXXX
  • 07XXXXXXXX
  • 947XXXXXXXX
  • +94 7X XXX XXXX
  • Variations with spaces, dashes, or parentheses

Upload behavior:

  • CSV importer auto-cleans name, email, and phone fields
  • Rows with only contact_number are valid and supported
  • Invalid rows are shown with row numbers and reasons
  • Valid cleaned rows can be used immediately as a temporary imported source
  • Saving to recipients.csv is optional
  • Duplicate phone numbers are automatically deduplicated (first row kept)

Clean Raw Call Sheets or Lead Dumps

If you have a larger exported sheet with extra columns, place it in resources/input/ and run:

python clean_recipient_batches.py

What the cleaner does:

  • keeps only contact_number, name, and email
  • requires a valid Sri Lankan mobile number
  • drops any row where Payment Details or Registered is truthy
  • removes rows with missing or invalid numbers
  • removes duplicate numbers and keeps the first valid occurrence
  • blanks invalid email values instead of dropping otherwise-valid rows
  • writes <original-name>_cleaned.csv into resources/output/
  • skips files that already have a cleaned output unless you pass --force

This is useful for raw call sheets, CRM exports, and spreadsheet dumps that contain many non-SMS columns.

Name Handling:

  • Names with more than 2 words are automatically limited to the first 2 words
  • Example: "Muhammad Abdullah Hassan" → "Muhammad Abdullah"
  • This applies to both uploaded CSVs and manually added recipients
  • Prevents long names in personalization

File Selection at Campaign Time

  • Use sidebar selector to choose which CSV to use
  • Shows recipient count for selected file
  • Defaults to Sample
  • Automatically switches to uploaded file when created

📋 Configuration

SMS Credentials (.env)

Create .env file from .env.sample with your Text-Ware credentials:

SMS_USERNAME=your_textware_username_here
SMS_PASSWORD=your_textware_password_here
SMS_SOURCE=YOUR_SENDER_ID
SMS_API_URL=https://msg.text-ware.com/send_sms.php

Security:

  • .env is in .gitignore - never committed to repo
  • ✅ Only .env.sample is shared - acts as template
  • ✅ Credentials are local-only
  • ✅ Safe to commit the project

Recipients File

Two options available:

  1. Sample source (default)

    • Built-in test data
    • Always available
    • Backed by resources/sample-recipients.csv
    • Good for testing
  2. recipients.csv (your uploads)

    • Optional saved list on disk
    • Persists between sessions
    • Can be edited through the dashboard or manually in a text editor / spreadsheet tool

📱 SMS Message Template

Default Template:

The default SMS text is loaded from resources/message_template.txt.

Dear Student, {name}

We're excited to announce that the CCA Bootcamp Programs Inauguration
Ceremony will be held today.

This will be the official launch session for our bootcamp programs under
BYOW, DDIGITAL, RANDS, and VAT0. We warmly invite you to join us and be
part of this important beginning.

Date: 22 March 2026
Time: 9.00 PM
Platform: Zoom

Join Link: https://us06web.zoom.us/j/85143454719?pwd=...

Passcode: 331423

We look forward to having you with us tonight.

SITC Campus X CodeZela

Personalization:

  • Use {name} placeholder for recipient's name
  • Example: "Dear Student, Sayuru Akash"
  • If name missing, sends without replacement

Customize in Streamlit Dashboard:

  • Go to Campaigns > Message Template
  • Edit text directly in the web UI
  • The edited message is kept in Streamlit session state for the current app session
  • Sending from the dashboard uses the edited message directly without rewriting Python files
  • This is the recommended way to adjust a campaign message before sending

Or edit the default template file:

resources/message_template.txt
  • This controls the default message used by the CLI and by new Streamlit sessions
  • Streamlit message edits do not rewrite this file
  • Edit this file only when you want to change the default template for future runs

⚙️ Performance & Configuration

Default Settings

Setting Value Adjustable
Rate limit 2 seconds Yes (1-10 in UI)
Retry attempts 3 times Code only
Timeout 30 seconds Code only
API method POST Auto fallback to GET

Speed Estimates

  • Speed: ~30 SMS/minute (with 2-sec rate limit)
  • 100 recipients: ~3-4 minutes
  • 500 recipients: ~15-20 minutes
  • 1000 recipients: ~30-40 minutes
  • Success rate: 99%+ with retry logic

Customize Rate Limit

  • In Dashboard: Campaigns tab > Rate Limit slider
  • In Code: Edit sms_sender.py, line with self.rate_limit_delay

Customize API Retry

Edit sms_sender.py:

max_retries = 3  # Change this value
timeout = 30  # Request timeout in seconds

📊 Logging & Reports

Campaign Reports

Location: reports/sms_report_YYYYMMDD_HHMMSS.json

Contains:

  • Report version and generated timestamp
  • Campaign start and finish timestamps
  • Total recipient rows processed
  • Gateway acceptance, error, and skipped breakdown
  • Run context such as channel, source, and message template
  • Individual SMS results:
    • Iteration number
    • Status (success, error, or skipped)
    • Phone number / contact number
    • Recipient name & email
    • Full message body
    • Message preview
    • API response
    • Operation ID when available
    • Error details (if failed)

Note: a successful send in this app means the SMS gateway accepted the request. It does not by itself confirm handset delivery.

View report:

cat reports/sms_report_*.json | python -m json.tool

Application Logs

Location: logs/sms_sender_YYYYMMDD_HHMMSS.log

Contains:

  • All API calls and responses
  • Error messages with context
  • Rate limiting info
  • Retry attempts
  • Success confirmations

View logs:

tail -f logs/sms_sender_*.log

🧪 Testing

Automated Test Suite

Run the full suite:

python -m pytest

Run with coverage:

python -m pytest --cov=. --cov-report=term-missing

The suite covers:

  • core SMS sender helpers and API-request behavior
  • bulk sending, reports, and fallback paths
  • CLI entry points in main.py
  • quickstart/menu flows in quickstart.py
  • Streamlit app state and recipient workflows
  • batch-cleaning script behavior for pending and one-off CSV imports

Current local verification:

  • 110 tests passing
  • 93% total coverage from python -m pytest --cov=. --cov-report=term-missing

Quick Test: Web Dashboard

python main.py --streamlit
  1. Open http://localhost:8501
  2. Go to Campaigns tab
  3. Click Send Test SMS
  4. Check report in Reports tab

Test via Command Line

python main.py --test

Sends one SMS to first recipient in CSV.

Full Campaign

python main.py --bulk

Sends to all recipients with confirmation.


✅ Pre-Commit Checklist

  • .env file is in .gitignore (credentials never committed)
  • .env.sample included as template
  • Virtual environment in .gitignore
  • __pycache__/ in .gitignore
  • Logs and reports auto-generated (safe to ignore)
  • No hardcoded credentials in code
  • README updated with all features
  • All required files present

Before commit, verify:

git status  # Should show no .env file
cat .gitignore | grep -E "\.env|venv"  # Should find both

❌ Troubleshooting

Problem Solution
Streamlit not starting Ensure venv is activated: source venv/bin/activate
Module not found errors Install dependencies: pip install -r requirements.txt
".env not found" Create .env from .env.sample with your credentials
No recipients showing Use the bundled Sample source or upload a custom file
Cleaner says phone column missing Rename or map your phone column to a supported header such as Phone Number, contact_number, or mobile
API connection failed Check internet, verify SMS credentials in .env
SMS not sending Check phone format (07XXXXXXXX for Sri Lanka or full international)
Port 8501 already in use Stop the existing process with `lsof -ti:8501
venv not activating Recreate: python3 -m venv venv && source venv/bin/activate

Debug steps:

  1. Check if venv is active: You should see (venv) in terminal
  2. View logs: cat logs/sms_sender_*.log
  3. Check reports: cat reports/sms_report_*.json | python -m json.tool
  4. Test credentials: echo $SMS_USERNAME (should show username)
  5. Verify bundled sample: head -5 resources/sample-recipients.csv
  6. Verify pending raw inputs: find resources/input -maxdepth 1 -name "*.csv"

🔐 Security & Privacy

Credential Safety:

  • .env file is .gitignored - never committed
  • ✅ Only .env.sample in repo as reference
  • ✅ Credentials never logged or displayed
  • ✅ API calls use HTTPS only
  • ✅ No hardcoded secrets in code

Data Protection:

  • ✅ Recipient data in transit via HTTPS
  • ✅ Reports saved locally (not uploaded)
  • ✅ Logs contain no passwords
  • ✅ Can safely commit project to public repo

Best Practices:

  • Never share .env file
  • Rotate credentials periodically
  • Monitor logs for errors
  • Keep dependencies updated

📈 API Information

Provider: Text-Ware SMS Gateway

Request Details:

  • Endpoint: https://msg.text-ware.com/send_sms.php
  • Method: POST (with GET fallback)
  • Protocol: HTTPS
  • Timeout: 30 seconds

Required Parameters:

  • username - API username (from .env)
  • password - API password (from .env)
  • src - Sender ID (from SMS_SOURCE)
  • dst - Destination phone number
  • text - Message body

Response:

  • Format: JSON
  • Success: HTTP 200 with gateway acceptance response, often including an operation ID
  • Error: HTTP 400+ with error message

Delivery-status note:

  • TextWare's public API docs show send endpoints and examples that include a dr flag, but this project does not currently have a documented public delivery-status lookup endpoint to poll
  • The app therefore records gateway acceptance and operation IDs, but does not claim confirmed handset delivery

Retry Strategy:

  • Automatic on: 429, 500, 502, 503, 504
  • Backoff: 1 second between attempts
  • Max retries: 3 (configurable)

📞 Dependencies

Required packages:

requests           # HTTP library
python-dotenv      # Environment variables
pandas             # CSV handling
streamlit          # Web dashboard
watchdog           # Faster Streamlit file watching on macOS/Linux
pytest             # Test runner
pytest-cov         # Coverage reporting

Install all:

pip install -r requirements.txt

✅ Quick Checklist

Before first use:

  • .env exists with SMS credentials
  • Bundled sample source is available, or upload/create recipients.csv
  • pip install -r requirements.txt completed
  • Internet connection working
  • Python 3.8+ installed

🎯 Common Tasks

Add New Recipients

Best option in the dashboard:

  • Use Recipients > Upload CSV > Use now for a temporary in-memory list
  • Use Save as recipients.csv only when you want a reusable saved list

Manual file edit also works when needed. Edit recipients.csv and add rows:

name,email,contact_number
New Person,email@example.com,07XXXXXXXX

Or use a phone-only list:

contact_number
07XXXXXXXX

Clean a Raw Export

python clean_recipient_batches.py

For a one-off file:

python clean_recipient_batches.py --file "resources/input/your-file.csv"

Change Message

  • Recommended: edit the message directly in the Streamlit Campaigns tab
  • Optional: edit resources/message_template.txt if you want to change the default template shown in new app sessions and used by the CLI

Adjust Speed

Reduce rate_limit_delay in sms_sender.py (faster but more API requests)

Increase Reliability

Increase max_retries in sms_sender.py (slower but fewer failures)

View Campaign Results

cat reports/sms_report_*.json | python -m json.tool

Monitor Sending

tail -f logs/sms_sender_*.log

📊 Example Workflow

Step 1: Choose recipients

Recommended path:

  • Start with the Sample source for a quick end-to-end test
  • Or upload your own CSV and click Use now to work without writing a file

Optional saved path:

# Edit recipients.csv with your recipients
nano recipients.csv

Step 2: Customize message (optional)

Recommended path:

  • Edit the draft message directly in the Streamlit Campaigns tab
  • This uses the updated message immediately without rewriting files

Optional default-template edit:

# Edit the default message template if needed
nano resources/message_template.txt

Step 3: Send test SMS

python main.py --test

Step 4: Check result

cat reports/sms_report_*.json | python -m json.tool

Step 5: Send full campaign

python main.py --bulk

Step 6: Review results

cat reports/sms_report_*.json | python -m json.tool
tail -f logs/sms_sender_*.log

🎓 Understanding the System

How SMS Sending Works

  1. Load recipients from the selected source (Sample, recipients.csv, or an imported in-memory list)
  2. Personalize message - replace {name} with recipient name
  3. Send SMS via Text-Ware API
  4. Rate limit - wait 2 seconds before next SMS
  5. Handle response - log result
  6. Retry on failure - up to 3 attempts with backoff
  7. Generate report - save all results to JSON
  8. Create logs - record all events

Error Handling

  • Network timeout → Automatic retry
  • Connection error → Automatic retry with backoff
  • HTTP error (400-599) → Log and report
  • Missing data → Skip with warning
  • Invalid CSV → Error message with details

Rate Limiting

  • Prevents API overload by spacing requests
  • 2 seconds between SMS (default)
  • Configurable based on API limits
  • Important for reliability

Retry Logic

  • Tries 3 times on failure
  • Exponential backoff: 1s, 2s, 4s
  • Handles transient failures
  • Comprehensive error reporting

🚀 Getting Started

  1. Clone/download project to /Users/sayuru/PycharmProjects/sms-sender-python-textware

  2. Install dependencies:

    source venv/bin/activate
    pip install -r requirements.txt
  3. Verify setup:

    python main.py --streamlit
  4. Choose recipients: start with the Sample source, upload a CSV and click Use now, or edit recipients.csv if you want a saved list

  5. Send SMS:

    python main.py --bulk
  6. View results:

    cat reports/sms_report_*.json | python -m json.tool

Troubleshooting

If something is not working as expected, start with these checks:

  1. Review logs/sms_sender_*.log for request, retry, and error details
  2. Inspect reports/sms_report_*.json to confirm which recipients succeeded, failed, or were skipped
  3. Run python main.py --test to verify credentials and API connectivity before a full campaign
  4. Confirm .env contains valid Text-Ware credentials and sender configuration
  5. Reinstall dependencies with pip install --upgrade -r requirements.txt if your environment is out of sync

For Streamlit-specific issues, restart the app with:

python main.py --streamlit

For test execution and local verification, use:

python -m pytest --cov=. --cov-report=term-missing

Repository

  • GitHub: sayuru-akash/sms-sender-python-textware
  • Dashboard: python main.py --streamlit
  • CLI test send: python main.py --test
  • Full campaign: python main.py --bulk
  • Interactive menu: python quickstart.py

Issues

For bugs, regressions, or feature requests, use the GitHub issue tracker:

When reporting a problem, include:

  • the command you ran
  • whether you used sample-recipients.csv, recipients.csv, or an imported in-memory list
  • the relevant error from logs/sms_sender_*.log
  • the related report file from reports/ if a campaign started

Contributing

Contributions are welcome through GitHub pull requests:

  1. Fork the repository
  2. Create a branch for your change
  3. Run pytest --cov=. --cov-report=term-missing
  4. Update documentation when behavior changes
  5. Open a pull request with a clear summary

Keep changes focused, avoid committing .env, and include tests for any behavior change in the sender, CLI, or Streamlit app.

About

Simple bulk SMS sending solution using TextWare API with added rate limiting, error handling, logging, and reporting.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages