{"id":13564787,"url":"https://github.com/allisson/postmand","last_synced_at":"2025-04-03T21:31:47.355Z","repository":{"id":41364561,"uuid":"342553887","full_name":"allisson/postmand","owner":"allisson","description":"Simple webhook delivery system powered by Golang and PostgreSQL","archived":false,"fork":false,"pushed_at":"2024-10-16T13:01:18.000Z","size":274,"stargazers_count":25,"open_issues_count":2,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-04T18:44:46.286Z","etag":null,"topics":["hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Go","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/allisson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2021-02-26T11:23:20.000Z","updated_at":"2024-10-16T12:59:46.000Z","dependencies_parsed_at":"2024-01-16T18:58:14.537Z","dependency_job_id":"74885ab5-0e73-4658-8fff-9cd05f940351","html_url":"https://github.com/allisson/postmand","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fpostmand","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fpostmand/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fpostmand/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fpostmand/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allisson","download_url":"https://codeload.github.com/allisson/postmand/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247083711,"owners_count":20880897,"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","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":["hacktoberfest"],"created_at":"2024-08-01T13:01:35.863Z","updated_at":"2025-04-03T21:31:46.859Z","avatar_url":"https://github.com/allisson.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# postmand\n[![Build Status](https://github.com/allisson/postmand/workflows/release/badge.svg)](https://github.com/allisson/postmand/actions)\n[![Go Report Card](https://goreportcard.com/badge/github.com/allisson/postmand)](https://goreportcard.com/report/github.com/allisson/postmand)\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat-square)](https://pkg.go.dev/github.com/allisson/postmand)\n\nSimple webhook delivery system powered by Golang and PostgreSQL.\n\n## Features\n\n- Simple rest api with only three endpoints (webhooks/deliveries/delivery-attempts).\n- Select the status codes that are considered valid for a delivery.\n- Control the maximum amount of delivery attempts and delay between these attempts (min and max backoff).\n- Locks control of worker deliveries using PostgreSQL SELECT FOR UPDATE SKIP LOCKED.\n- Sending the X-Hub-Signature header if the webhook is configured with a secret token.\n- Simplicity, it does the minimum necessary, it will not have authentication/permission scheme among other things, the idea is to use it internally in the cloud and not leave exposed.\n\n## Quickstart\n\nLet's start with the basic concepts, we have three main entities that we must know to start:\n\n- Webhook: The configuration of the webhook.\n- Delivery: The content sent to a webhook.\n- Delivery Attempt: An attempt to deliver the content to the webhook.\n\n### Run the server\n\nTo run the server it is necessary to have a database available from postgresql, in this example we will consider that we have a database called postmand running in localhost with user and password equal to user.\n\n```bash\ndocker run --name postgres --restart unless-stopped -e POSTGRES_USER=user -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=postmand -p 5432:5432 -d postgres:12-alpine\n```\n\n#### Docker\n\n```bash\ndocker run --rm --env POSTMAND_DATABASE_URL='postgres://user:password@host.docker.internal:5432/postmand?sslmode=disable' quay.io/allisson/postmand migrate # create database schema\n```\n\n```bash\ndocker run -p 8000:8000 -p 8001:8001 --env POSTMAND_DATABASE_URL='postgres://user:password@host.docker.internal:5432/postmand?sslmode=disable' quay.io/allisson/postmand server # run the server\n```\n\n#### Local\n\nInstall just command runner: https://github.com/casey/just?tab=readme-ov-file#installation\n\n```bash\ngit clone https://github.com/allisson/postmand\ncd postmand\ncp local.env .env # and edit .env\njust db-migrate # create database schema\njust run-server # run the server\njust run-worker\n```\n\n###  Run the worker\n\nThe worker is responsible to delivery content to the webhooks.\n\n#### Docker\n\n```bash\ndocker run --env POSTMAND_DATABASE_URL='postgres://user:pass@host.docker.internal:5432/postmand?sslmode=disable' quay.io/allisson/postmand worker\n```\n\n#### Local\n\n```bash\njust run-worker\ngo run cmd/postmand/main.go worker\n```\n\n### Create a new webhook\n\nThe fields delivery_attempt_timeout/retry_min_backoff/retry_max_backoff are in seconds.\n\n```bash\ncurl --location --request POST 'http://localhost:8000/v1/webhooks' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n    \"name\": \"Httpbin Post\",\n    \"url\": \"https://httpbin.org/post\",\n    \"content_type\": \"application/json\",\n    \"valid_status_codes\": [\n        200,\n        201\n    ],\n    \"secret_token\": \"my-secret-token\",\n    \"active\": true,\n    \"max_delivery_attempts\": 5,\n    \"delivery_attempt_timeout\": 1,\n    \"retry_min_backoff\": 10,\n    \"retry_max_backoff\": 60\n}'\n```\n\n```javascript\n{\n  \"id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n  \"name\":\"Httpbin Post\",\n  \"url\":\"https://httpbin.org/post\",\n  \"content_type\":\"application/json\",\n  \"valid_status_codes\":[\n    200,\n    201\n  ],\n  \"secret_token\":\"my-secret-token\",\n  \"active\":true,\n  \"max_delivery_attempts\":5,\n  \"delivery_attempt_timeout\":1,\n  \"retry_min_backoff\":10,\n  \"retry_max_backoff\":60,\n  \"created_at\":\"2021-03-08T20:41:25.433671Z\",\n  \"updated_at\":\"2021-03-08T20:41:25.433671Z\"\n}\n```\n\n### Create a new delivery\n\n```bash\ncurl --location --request POST 'http://localhost:8000/v1/deliveries' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n    \"webhook_id\": \"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n    \"payload\": \"{\\\"success\\\": true}\"\n}'\n```\n\n```javascript\n{\n  \"id\":\"bc76122c-e56b-45c7-8dc3-b80a861191d5\",\n  \"webhook_id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n  \"payload\":\"{\\\"success\\\": true}\",\n  \"scheduled_at\":\"2021-03-08T20:43:49.986771Z\",\n  \"delivery_attempts\":0,\n  \"status\":\"pending\",\n  \"created_at\":\"2021-03-08T20:43:49.986771Z\",\n  \"updated_at\":\"2021-03-08T20:43:49.986771Z\"\n}\n```\n\n### Get deliveries\n\n```bash\ncurl --location --request GET 'http://localhost:8000/v1/deliveries?webhook_id=a6e9a525-ac5a-488c-b118-bd7327ce6d8d'\n```\n\n```javascript\n{\n  \"deliveries\":[\n    {\n      \"id\":\"bc76122c-e56b-45c7-8dc3-b80a861191d5\",\n      \"webhook_id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n      \"payload\":\"{\\\"success\\\": true}\",\n      \"scheduled_at\":\"2021-03-08T20:43:49.986771Z\",\n      \"delivery_attempts\":1,\n      \"status\":\"succeeded\",\n      \"created_at\":\"2021-03-08T20:43:49.986771Z\",\n      \"updated_at\":\"2021-03-08T20:46:51.674623Z\"\n    }\n  ],\n  \"limit\":50,\n  \"offset\":0\n}\n```\n\n### Get delivery\n\n```bash\ncurl --location --request GET 'http://localhost:8000/v1/deliveries/bc76122c-e56b-45c7-8dc3-b80a861191d5'\n```\n\n```javascript\n{\n  \"id\":\"bc76122c-e56b-45c7-8dc3-b80a861191d5\",\n  \"webhook_id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n  \"payload\":\"{\\\"success\\\": true}\",\n  \"scheduled_at\":\"2021-03-08T20:43:49.986771Z\",\n  \"delivery_attempts\":1,\n  \"status\":\"succeeded\",\n  \"created_at\":\"2021-03-08T20:43:49.986771Z\",\n  \"updated_at\":\"2021-03-08T20:46:51.674623Z\"\n}\n```\n\n### Get delivery attempts\n\n```bash\ncurl --location --request GET 'http://localhost:8000/v1/delivery-attempts?delivery_id=bc76122c-e56b-45c7-8dc3-b80a861191d5'\n```\n\n```javascript\n{\n  \"delivery_attempts\":[\n    {\n      \"id\":\"d72719d6-5a79-4df7-a2c2-2029ab0e1848\",\n      \"webhook_id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n      \"delivery_id\":\"bc76122c-e56b-45c7-8dc3-b80a861191d5\",\n      \"raw_request\":\"POST /post HTTP/1.1\\r\\nHost: httpbin.org\\r\\nContent-Type: application/json\\r\\nX-Hub-Signature: 3fc5d4b8ff4efb404be24faf543667d29902d6a1306bd0c1ef2084497300cee9\\r\\n\\r\\n{\\\"success\\\": true}\",\n      \"raw_response\":\"HTTP/2.0 200 OK\\r\\nContent-Length: 538\\r\\nAccess-Control-Allow-Credentials: true\\r\\nAccess-Control-Allow-Origin: *\\r\\nContent-Type: application/json\\r\\nDate: Mon, 08 Mar 2021 20:46:51 GMT\\r\\nServer: gunicorn/19.9.0\\r\\n\\r\\n{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"{\\\\\\\"success\\\\\\\": true}\\\", \\n  \\\"files\\\": {}, \\n  \\\"form\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept-Encoding\\\": \\\"gzip\\\", \\n    \\\"Content-Length\\\": \\\"17\\\", \\n    \\\"Content-Type\\\": \\\"application/json\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"User-Agent\\\": \\\"Go-http-client/2.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-60468d3b-36d312777a03ec3e1c564e3b\\\", \\n    \\\"X-Hub-Signature\\\": \\\"3fc5d4b8ff4efb404be24faf543667d29902d6a1306bd0c1ef2084497300cee9\\\"\\n  }, \\n  \\\"json\\\": {\\n    \\\"success\\\": true\\n  }, \\n  \\\"origin\\\": \\\"191.35.122.74\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\",\n      \"response_status_code\":200,\n      \"execution_duration\":547,\n      \"success\":true,\n      \"error\":\"\",\n      \"created_at\":\"2021-03-08T20:46:51.680846Z\"\n    }\n  ],\n  \"limit\":50,\n  \"offset\":0\n}\n```\n\n### Get delivery attempt\n\n```bash\ncurl --location --request GET 'http://localhost:8000/v1/delivery-attempts/d72719d6-5a79-4df7-a2c2-2029ab0e1848'\n```\n\n```javascript\n{\n  \"id\":\"d72719d6-5a79-4df7-a2c2-2029ab0e1848\",\n  \"webhook_id\":\"a6e9a525-ac5a-488c-b118-bd7327ce6d8d\",\n  \"delivery_id\":\"bc76122c-e56b-45c7-8dc3-b80a861191d5\",\n  \"raw_request\":\"POST /post HTTP/1.1\\r\\nHost: httpbin.org\\r\\nContent-Type: application/json\\r\\nX-Hub-Signature: 3fc5d4b8ff4efb404be24faf543667d29902d6a1306bd0c1ef2084497300cee9\\r\\n\\r\\n{\\\"success\\\": true}\",\n  \"raw_response\":\"HTTP/2.0 200 OK\\r\\nContent-Length: 538\\r\\nAccess-Control-Allow-Credentials: true\\r\\nAccess-Control-Allow-Origin: *\\r\\nContent-Type: application/json\\r\\nDate: Mon, 08 Mar 2021 20:46:51 GMT\\r\\nServer: gunicorn/19.9.0\\r\\n\\r\\n{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"{\\\\\\\"success\\\\\\\": true}\\\", \\n  \\\"files\\\": {}, \\n  \\\"form\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept-Encoding\\\": \\\"gzip\\\", \\n    \\\"Content-Length\\\": \\\"17\\\", \\n    \\\"Content-Type\\\": \\\"application/json\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"User-Agent\\\": \\\"Go-http-client/2.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-60468d3b-36d312777a03ec3e1c564e3b\\\", \\n    \\\"X-Hub-Signature\\\": \\\"3fc5d4b8ff4efb404be24faf543667d29902d6a1306bd0c1ef2084497300cee9\\\"\\n  }, \\n  \\\"json\\\": {\\n    \\\"success\\\": true\\n  }, \\n  \\\"origin\\\": \\\"191.35.122.74\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\",\n  \"response_status_code\":200,\n  \"execution_duration\":547,\n  \"success\":true,\n  \"error\":\"\",\n  \"created_at\":\"2021-03-08T20:46:51.680846Z\"\n}\n```\n\n### Swagger docs\n\nThe swagger spec is available at http://localhost:8000/swagger/index.html.\n\n### Health check\n\nThe health check server is running on port defined by envvar POSTMAND_HEALTH_CHECK_HTTP_PORT (defaults to 8001).\n\n```bash\ncurl --location --request GET 'http://localhost:8001/healthz'\n```\n\n```javascript\n{\n  \"success\":true\n}\n```\n\n### Environment variables\n\nAll environment variables is defined on file local.env.\n\n## How to build docker image\n\n```\ndocker build -f Dockerfile -t postmand .\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Fpostmand","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallisson%2Fpostmand","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Fpostmand/lists"}