{"id":34995348,"url":"https://github.com/realharis/hourly-ayah-mailer-aws-lambda-event-bridge","last_synced_at":"2026-05-25T14:04:21.553Z","repository":{"id":325518615,"uuid":"1101273414","full_name":"RealHaris/hourly-ayah-mailer-aws-lambda-event-bridge","owner":"RealHaris","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-10T07:46:19.000Z","size":176,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-10T09:55:58.584Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RealHaris.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-21T12:49:47.000Z","updated_at":"2025-12-10T07:46:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/RealHaris/hourly-ayah-mailer-aws-lambda-event-bridge","commit_stats":null,"previous_names":["realharis/hourly-ayah-mailer-aws-lambda-event-bridge"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/RealHaris/hourly-ayah-mailer-aws-lambda-event-bridge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealHaris%2Fhourly-ayah-mailer-aws-lambda-event-bridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealHaris%2Fhourly-ayah-mailer-aws-lambda-event-bridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealHaris%2Fhourly-ayah-mailer-aws-lambda-event-bridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealHaris%2Fhourly-ayah-mailer-aws-lambda-event-bridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RealHaris","download_url":"https://codeload.github.com/RealHaris/hourly-ayah-mailer-aws-lambda-event-bridge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealHaris%2Fhourly-ayah-mailer-aws-lambda-event-bridge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28069214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-27T02:00:05.897Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-27T02:03:47.269Z","updated_at":"2025-12-27T02:04:59.171Z","avatar_url":"https://github.com/RealHaris.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Quran Hourly Ayah Mailer (AWS SAM, Node.js)\n\nHourly Lambda fetches a random Quran ayah (Arabic + English), stores it in DynamoDB, and emails all contacts using Gmail (app password). Includes HTTP APIs to manage contacts and manually trigger sends.\n\n### Architecture\n- EventBridge Rule (every hour) → Lambda `ScheduledSendFunction`\n- Quran API: `https://api.alquran.cloud`\n- Data:\n  - DynamoDB `Contacts` (pk: `id`, GSI: `EmailIndex` on `email`)\n  - DynamoDB `Ayahs`    (pk: `id`)\n- Email: Gmail SMTP via Nodemailer. Gmail credentials are stored in AWS Secrets Manager (`gmail/ayah-mailer`).\n- HTTP API (API Gateway HTTP API) endpoints:\n  - POST `/send/all`\n  - POST `/send/contact` (by contact id in body)\n  - POST `/send/direct` (by raw email in body)\n  - POST `/contacts`\n  - DELETE `/contacts/{id}`\n  - GET `/contacts`\n  - GET `/unsubscribe?id={id}`\n\n### Prerequisites\n- AWS CLI, SAM CLI, Node.js 20\n- AWS account/credentials configured (IAM user or SSO)\n- Docker Desktop (for local testing with SAM Local)\n\n### Local Testing\n\n**How environment variables work**: They're automatically set from `template.yaml` during deployment - no `.env` file needed! See [LOCAL_TESTING.md](LOCAL_TESTING.md) for details. During deployment we also expose `HTTP_API_URL` to functions so emails can include a working unsubscribe link.\n\n**Quick local test**:\n```powershell\n# Build\nsam build\n\n# Test a function locally (requires AWS credentials and real DynamoDB tables)\nsam local invoke ListContactsFunction --env-vars env.json\n\n# Or run local API server\nsam local start-api --env-vars env.json\n```\n\nSee [LOCAL_TESTING.md](LOCAL_TESTING.md) for complete local testing guide.\n\n### Quick start (PowerShell)\nSet your values:\n```powershell\n$Region   = \"us-east-1\"\n$Stack    = \"quran-hourly-mailer\"\n$MailFrom = \"you@gmail.com\"\n$Proj     = \"D:\\apps\\wrok\\extras\\aws-lambda\"\n```\n\nInstall tools (if needed):\n```powershell\nwinget install -e --id Amazon.AWSCLI --source winget --accept-source-agreements --accep-package-agreements\nwinget install -e --id AWS.AWSSAMCLI --source winget --accept-source-agreements --accept-package-agreements\nwinget install -e --id OpenJS.NodeJS.LTS --source winget --accept-source-agreements --accept-package-agreements\n```\n\nVerify:\n```powershell\naws --version\nsam --version\nnode -v\nnpm -v\n```\n\nConfigure AWS (one of):\n```powershell\n# A) Access keys\naws configure set region $Region\naws configure set aws_access_key_id \"YOUR_ACCESS_KEY_ID\"\naws configure set aws_secret_access_key \"YOUR_SECRET_ACCESS_KEY\"\n\n# B) SSO\naws configure sso\naws sso login\n```\n\nInstall deps:\n```powershell\nSet-Location $Proj\nnpm ci --omit=dev\n```\n\nCreate/Update Gmail Secrets Manager secret:\n```powershell\n# First-time create:\n$AppPassword = \"YOUR_APP_PASSWORD\"   # use your Gmail app password\n$SecretJson = @{ username=$MailFrom; app_password=$AppPassword } | ConvertTo-Json -Compress\naws secretsmanager create-secret --name \"gmail/ayah-mailer\" --secret-string $SecretJson --region $Region\n\n# If exists, update:\n# aws secretsmanager put-secret-value --secret-id \"gmail/ayah-mailer\" --secret-string $SecretJson --region $Region\n```\n\nBuild and deploy:\n```powershell\nsam build\nsam deploy `\n  --stack-name $Stack `\n  --region $Region `\n  --capabilities CAPABILITY_IAM `\n  --resolve-s3 `\n  --no-confirm-changeset `\n  --parameter-overrides \"GmailSecretId=gmail/ayah-mailer\" \"MailFrom=$MailFrom\"\n```\n\nGet API URL:\n```powershell\n$StackDesc = aws cloudformation describe-stacks --stack-name $Stack --region $Region | ConvertFrom-Json\n$ApiUrl = ($StackDesc.Stacks[0].Outputs | Where-Object { $_.OutputKey -eq 'HttpApiUrl' }).OutputValue\n$ApiUrl\n```\n\n### API quick tests (PowerShell)\n```powershell\n# Add contact\n$body = @{ email=\"recipient@example.com\"; name=\"Recipient\" } | ConvertTo-Json\n$created = Invoke-RestMethod -Method POST -Uri \"$ApiUrl/contacts\" -ContentType \"application/json\" -Body $body\n$id = $created.id\n\n# List contacts\nInvoke-RestMethod -Uri \"$ApiUrl/contacts\"\n\n# Send to one contact (by ID)\n$body = @{ id=\"$id\" } | ConvertTo-Json\nInvoke-RestMethod -Method POST -Uri \"$ApiUrl/send/contact\" -ContentType \"application/json\" -Body $body\n\n# Send direct (by email)\n$body = @{ email=\"recipient@example.com\" } | ConvertTo-Json\nInvoke-RestMethod -Method POST -Uri \"$ApiUrl/send/direct\" -ContentType \"application/json\" -Body $body\n\n# Send to all contacts\nInvoke-RestMethod -Method POST -Uri \"$ApiUrl/send/all\"\n\n# Unsubscribe by id (simulates clicking the link in email)\nInvoke-RestMethod -Uri \"$ApiUrl/unsubscribe?id=$id\"\n# Remove a contact\nInvoke-RestMethod -Method DELETE -Uri \"$ApiUrl/contacts/$id\"\n```\n\n### API quick tests (cURL)\n```bash\n# Set your live base URL\nBASE_URL=\"https://{apiId}.execute-api.{region}.amazonaws.com/v1\"\n\n# List contacts\ncurl \"$BASE_URL/contacts\"\n\n# Add contact\nCREATE=$(curl -s -X POST \"$BASE_URL/contacts\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"recipient@example.com\",\"name\":\"Recipient\"}')\nID=$(echo \"$CREATE\" | sed -n 's/.*\"id\":\"\\([^\"]*\\)\".*/\\1/p')\n\n# Send to one contact (by ID)\ncurl -s -X POST \"$BASE_URL/send/contact\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"id\\\":\\\"$ID\\\"}\"\n\n# Send direct (by email)\ncurl -s -X POST \"$BASE_URL/send/direct\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"recipient@example.com\"}'\n\n# Send to all contacts\ncurl -X POST \"$BASE_URL/send/all\"\n\n# Unsubscribe by id\ncurl \"$BASE_URL/unsubscribe?id=$ID\"\n# Remove a contact\ncurl -X DELETE \"$BASE_URL/contacts/$ID\"\n```\n\n### Deploy new changes\n```powershell\nSet-Location $Proj\nsam build\nsam deploy `\n  --stack-name $Stack `\n  --region $Region `\n  --capabilities CAPABILITY_IAM `\n  --resolve-s3 `\n  --no-confirm-changeset `\n  --parameter-overrides \"GmailSecretId=gmail/ayah-mailer\" \"MailFrom=$MailFrom\"\n```\n\n### View EventBridge rule and logs\n```powershell\n# List EventBridge rules in the stack's region\naws events list-rules --region $Region\n\n# Get Scheduled Lambda name from stack resources\n$Res = aws cloudformation describe-stack-resources --stack-name $Stack --region $Region | ConvertFrom-Json\n$ScheduledFn = ($Res.StackResources | Where-Object { $_.LogicalResourceId -eq 'ScheduledSendFunction' }).PhysicalResourceId\n\n# Tail last hour logs from scheduled function\naws logs tail \"/aws/lambda/$ScheduledFn\" --region $Region --since 1h\n```\n\n### View current DynamoDB data\n```powershell\n# List tables\naws dynamodb list-tables --region $Region\n\n# Scan Contacts\naws dynamodb scan --table-name Contacts --region $Region\n\n# Scan Ayahs\naws dynamodb scan --table-name Ayahs --region $Region\n```\n\n### See serverless (stack) status\n```powershell\n# Describe stack status and outputs\naws cloudformation describe-stacks --stack-name $Stack --region $Region\n\n# List stack resources\naws cloudformation describe-stack-resources --stack-name $Stack --region $Region\n```\n\n### View each Lambda’s logs\n```powershell\n$Res = aws cloudformation describe-stack-resources --stack-name $Stack --region $Region | ConvertFrom-Json\n$FnIds = @(\n  'ScheduledSendFunction',\n  'SendAllFunction',\n  'SendContactFunction',\n  'AddContactFunction',\n  'DeleteContactFunction',\n  'ListContactsFunction'\n)\nforeach ($id in $FnIds) {\n  $fn = ($Res.StackResources | Where-Object { $_.LogicalResourceId -eq $id }).PhysicalResourceId\n  if ($fn) {\n    Write-Host \"=== $id ($fn) ===\"\n    aws logs tail \"/aws/lambda/$fn\" --region $Region --since 1h\n  }\n}\n```\n\n### Clean up\n```powershell\nsam delete --stack-name $Stack --region $Region\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealharis%2Fhourly-ayah-mailer-aws-lambda-event-bridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealharis%2Fhourly-ayah-mailer-aws-lambda-event-bridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealharis%2Fhourly-ayah-mailer-aws-lambda-event-bridge/lists"}