{"id":21010883,"url":"https://github.com/miserman/survey_scheduler","last_synced_at":"2026-04-14T06:31:59.257Z","repository":{"id":162995348,"uuid":"239890655","full_name":"miserman/survey_scheduler","owner":"miserman","description":"A Node.js app to schedule texts for experience sampling studies","archived":false,"fork":false,"pushed_at":"2024-07-24T21:17:24.000Z","size":472,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-13T15:43:38.235Z","etag":null,"topics":["aws","nodejs","sms","survey"],"latest_commit_sha":null,"homepage":"https://miserman.github.io/survey_scheduler","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/miserman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2020-02-12T00:04:12.000Z","updated_at":"2021-10-20T21:43:11.000Z","dependencies_parsed_at":"2023-12-13T15:39:29.605Z","dependency_job_id":"805788d9-bd8d-480a-95c0-8b9436606231","html_url":"https://github.com/miserman/survey_scheduler","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/miserman/survey_scheduler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miserman%2Fsurvey_scheduler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miserman%2Fsurvey_scheduler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miserman%2Fsurvey_scheduler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miserman%2Fsurvey_scheduler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/miserman","download_url":"https://codeload.github.com/miserman/survey_scheduler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miserman%2Fsurvey_scheduler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31785515,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["aws","nodejs","sms","survey"],"created_at":"2024-11-19T09:24:15.505Z","updated_at":"2026-04-14T06:31:59.238Z","avatar_url":"https://github.com/miserman.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# survey scheduler\n\nA Node.js app to schedule texts for experience sampling studies.\n\n\u003cimg src='docs/icon.png' width='200px'\u003e\n\n## demo\n\nThe [demo study](https://miserman.github.io/survey_scheduler/?study=demo) is a disintegrated version of the interface, which can be used to test the interface.\n\nParticipants can be added to the demo study individually through the add or edit menu, or automatically generated by specifying an **n** parameter in the URL; e.g., [miserman.github.io/survey_scheduler/?study=demo\u0026**n=50**](https://miserman.github.io/survey_scheduler/?study=demo\u0026n=50).\n\nGenerated participants are based on the default settings in the add or edit \u003e participant menu, which are update when changed -- change the day or time ranges, or protocol settings, and these will be applied to newly generated participants.\n\nClear local storage (menu \u003e clear storage), or change the specified **n** and refresh to generate new participants.\n\n## status codes\n\nBeeps have associated status codes to keep track of scheduling:\n\n0. _missed_ ![](https://placehold.it/20x10/f38d0e?text=+): Set when a pending beep is outside of its open window.\n1. _pending_ ![](https://placehold.it/20x10/7fb2ff?text=+): Set when a beep is scheduled; only pending beeps are ever sent.\n2. _sent_ ![](https://placehold.it/20x10/fdff81?text=+): Set when a beep has been sent.\n3. _reminded_ ![](https://placehold.it/20x10/f9c361?text=+): Set when a reminder for a sent beep has been sent.\n4. _send_received_ ![](https://placehold.it/20x10/dedede?text=+): Set after a beep has been sent, and a checkin with access has been received within the beep's window.\n5. _remind_received_ ![](https://placehold.it/20x10/dedede?text=+): Set after a reminder has been sent, and a checkin with access has been received within the beep's window.\n6. _pause_ ![](https://placehold.it/20x10/caf9f9?text=+): Set from the client, to prevent a passing beep from being sent.\n7. _skipped_ ![](https://placehold.it/20x10/8eab79?text=+): Set when a paused beep has passed.\n\nOutside of status codes, a beep might be colored a darker blue ![](https://placehold.it/20x10/0060ea?text=+) when it is the next pending beep to be sent, or black when a beep in the timeline is hovered over.\n\nIf delivery status logging is set up, beeps that were successfully sent to SNS (of status _sent_ or _reminded_) but not successfully delivered to the phone (as notified by Lambda) are marked by an asterisk (**\\***), and provider responses are displayed when the beep is hovered over.\n\n# running the app\n\nThe app has these requirements:\n\n1. Node.js (tested on version 14).\n1. A single, stable environment for scheduling. The app schedules beeps locally, so it has to be running when a beep is meant to be sent. Each time the app is started, it will initially schedule beeps upcoming within a week. If multiple instances of the app are running, beeps may be sent multiple times.\n1. Ability to receive HTTP requests for checkins from the survey.\n\nThe easiest way to run the app may be from Amazon's [Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/), but the app does require a secured connection for [Cognito](https://aws.amazon.com/cognito/)'s callback, which is easiest to set up with a load balancer on Elastic Beanstalk.\n\nAnother simple hosting option is Google's [App Engine](https://cloud.google.com/appengine/), but it will sometimes maintain multiple instances by default, so that may be something to manage.\n\n## services\n\nThe app uses these [Amazon Web Services](https://aws.amazon.com/) (AWS):\n\n[**Cognito**](https://aws.amazon.com/cognito/): To manage user accounts.\n\n1. From the [Cognito console](https://console.aws.amazon.com/cognito/users), make Create a user pool\n   1. In Policies, select Only allow administrators to create users\n   1. In App Clients, Add an app client, and check only Enable SRP\n   1. Create Pool\n1. Create an initial, administrative user with General settings \u003e Users and groups \u003e Create user\n1. Set up the App client in App integration \u003e App client settings:\n   1. Under Enabled Identity Providers, check Cognito User Pool\n   1. In Sign in sign out URLs, enter your URL, with /auth appended to the Callback URL (e.g., http://localhost:3000/auth for testing)\n   1. In OAuth 2.0, check Authorization code grant and aws.cognito.signin.user.admin\n\n[**DynamoDB**](https://aws.amazon.com/dynamodb/): To store study information and participant details.\n\n[**SNS**](https://aws.amazon.com/sns/): To send the SMS messages.\n\n#### AWS access\n\nThe app needs AWS access to run these services, which can be set up through [IAM](https://console.aws.amazon.com/iam/home#/users):\n\n1. Add user\n1. Name whatever, and check Programmatic access\n1. Select Attach existing policies directly, and add these policies:\n   - AmazonCognitoPowerUser\n   - AmazonDynamoDBFullAccess\n   - AmazonSNSFullAccess\n1. Add the Access key ID and Secret access key to a \"credentials\" file in, e.g., c:/users/name/.aws:\n\n```\n[default]\naws_access_key_id = Access key ID\naws_secret_access_key = Secret access key\n```\n\nAlternatively, if the app is running on a service with an IAM role, these policies can be attached to the role rather than a user.\n\n### Delivery Status Logging\n\nBy default, the app receives a message's ID when successfully sending it to SNS, but does not know if the message was successfully sent to the phone (the message's delivery status). To get delivery information, you can set up delivery status logging in SNS, and a Lambda function to send logged information to the app:\n\n1. Enable delivery status logging in [SNS](https://console.aws.amazon.com/sns):\n   - Mobile \u003e Text messaging (SMS) \u003e edit Text messaging preferences \u003e Delivery status logging\n1. Create a [Lambda](https://console.aws.amazon.com/lambda) function:\n   - Function \u003e Create Function\n   - Author from scratch\n   - Name whatever\n   - Node.js Runtime\n   - Create Function\n1. In the Designer section, Add trigger:\n   - CloudWatch Logs trigger\n   - In the Log group dropdown, you should see a DirectPublishToPhoneNumber/Failure group\n   - You can also add the DirectPublishToPhoneNumber group to receive all delivery responses\n   - Name whatever, other options default\n1. Copy in [functions/delivery_notifications.js](functions/delivery_notifications.js) as the function's code, replacing the hostname with your URL, then Save.\n\n### Qualtrics\n\nThe app is set up with [Qualtrics](https://www.qualtrics.com) in mind, though other platforms could be used. The app sends survey links with an added participant ID parameter, which the survey would need to extract in order to associate participants with responses through the link. In Qualtrics, you can get this by setting an Embedded Data variable matching the protocol's specified ID parameter:\n\n1. In a survey, select Survey Flow\n1. Add an Embedded Data element from the Add a New Element Here menu\n1. Create New Field matching your ID parameter (e.g., \"id\"), and leave its value blank.\n\nQualtrics can also checkin with the app when the survey is accessed:\n\n1. In a survey, select Survey Flow\n1. Add a Web Service element from the Add a New Element Here menu\n1. Enter your URL appended with /checkin\n1. Set Method to POST\n1. Add a body parameter, and set its Body Parameters to application/json, Parameter to your ID parameter, and set a String to the extracted ID via Piped Text (e.g., ${e://Field/id})\n1. If you want the checkin to also update the corresponding beep's status and access count, add a body parameter called \"access\" with a Boolean value of True. The \"access\" parameter can be used to separate an availability check from a status and accessed count update. For example, you might place a Web Services element without an \"access\" parameter at the start of the survey, and use it to gate access to the survey (if the survey is visited outside of a beep window, or more than allowed accesses), then add another Web Services element with an \"access\" parameter after the survey has been started. This would help avoid response time or loading issues in the case of limited allowed accesses (e.g., if the checkin goes through but the survey fails to fully load or receive a response in time, the survey can be refreshed without counting as another access).\n1. Finally, Add Embedded Data..., and set a value for available, accessed, day, days, beep, and beeps\n   ![](docs/example_webservice.png)\n\nIf the app recognizes the ID, it responds with an object like this:\n\n```javascript\n{\n  available: \"true\",\n  accessed: 3,\n  day: 0,\n  days: 12,\n  first_of_day: \"true\",\n  beep: 1,\n  beeps: 6\n}\n```\n\nHere, available is based on the most recently passed beep and the associated protocol's allowed accesses and close after settings. That is, available will be true if a beep had been accessed fewer than allowed accesses, and was sent no longer ago than the associated protocol's close after setting (or the associated protocol has no close after setting).\n\nThis information can be used from within Qualtrics to regulate access or condition questions on schedule status. For example, adding this as a question's JavaScript would prevent proceeding if available is false, and otherwise display schedule information:\n\n```javascript\nQualtrics.SurveyEngine.addOnload(function () {\n  var message = $('message'),\n    id = '${e://Field/id}',\n    response = {\n      first_of_day: '${e://Field/first_of_day}',\n      available: '${e://Field/available}',\n      accessed: '${e://Field/accessed}',\n      beeps: '${e://Field/beeps}',\n      beep: '${e://Field/beep}',\n      days: '${e://Field/days}',\n      day: '${e://Field/day}',\n    }\n  this.disableNextButton()\n  if (id !== '' \u0026\u0026 response.available === 'true') {\n    message.innerText =\n      'Participant ' +\n      id +\n      '; survey ' +\n      response.beep +\n      ' of ' +\n      response.beeps +\n      ' for day ' +\n      response.day +\n      ' of ' +\n      response.days +\n      ', accessed ' +\n      response.accessed +\n      ' times. This was ' +\n      (response.first_of_day === 'true' ? '' : 'not ') +\n      'their first access of the day.'\n    this.enableNextButton()\n  }\n})\n```\n\nHere, message refers to an HTML element in the question's body, with \"message\" as its id, e.g.:\n\n```html\n\u003cp id=\"message\"\u003eWait for a text to complete this survey.\u003c/p\u003e\n```\n\n## environment variables\n\n[server.js](https://github.com/miserman/survey_scheduler/blob/master/server.js) uses these environment variables:\n\n- **PORT**: The port the server listens to; often 3000 or 8081\n- **REGION**: AWS region, e.g., \"us-east-1\"\n- **USERPOOL**: Cognito Pool ID, from [User Pool](https://console.aws.amazon.com/cognito/users) \u003e General settings\n- **CLIENT**: Cognito App client ID, from [User Pool](https://console.aws.amazon.com/cognito/users) \u003e App integration \u003e App client settings\n- **DOMAIN**: Cognito domain, from [User Pool](https://console.aws.amazon.com/cognito/users) \u003e App integration \u003e Domain name\n- **REDIRECT**: URL set as the callback in [User Pool](https://console.aws.amazon.com/cognito/users) \u003e App integration \u003e App client settings; [server.js](https://github.com/miserman/survey_scheduler/blob/master/server.js) assumes this is /auth\n- **ADMIN**: Username of an initial user set up through [User Pool](https://console.aws.amazon.com/cognito/users) \u003e General settings \u003e Users and groups \u003e Create user; this user has full access to all studies; additional, study specific users should be created through the interface\n- **NOTIFICATIONS**: Optional topic for notifications about status updates and/or missed beeps; ARN from [SNS](https://console.aws.amazon.com/sns) \u003e Topics \u003e created topic\n- **DOUBLECHECK_FREQ**: Optional frequency in minutes to scan the local schedule for passed beeps that are still marked as pending (uncaught, missed beeps). If these beeps are caught in time, they will be sent. If any beeps are caught, they will be reported to the NOTIFICATIONS topic.\n- **REPORT_HOUR**: Optional hour (in the server's time) at which to send a daily status report to the NOTIFICATIONS topic, including number of sent and responded to, or skipped beeps. A report will only be sent if there were scheduled beeps since the last scheduled report, or since the app was started.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiserman%2Fsurvey_scheduler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmiserman%2Fsurvey_scheduler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiserman%2Fsurvey_scheduler/lists"}