agentmail-clone-v1 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/docs-deploy.yml +37 -0
  3. package/.github/workflows/landing-preview.yml +43 -0
  4. package/.github/workflows/openapi-lint.yml +31 -0
  5. package/.github/workflows/sdk-generate-check.yml +66 -0
  6. package/.github/workflows/sdk-release.yml +62 -0
  7. package/CHANGELOG.md +10 -0
  8. package/README.md +208 -0
  9. package/RELEASING.md +43 -0
  10. package/docs/api-reference/api-keys.mdx +11 -0
  11. package/docs/api-reference/domains.mdx +13 -0
  12. package/docs/api-reference/emails.mdx +26 -0
  13. package/docs/api-reference/inboxes.mdx +13 -0
  14. package/docs/api-reference/metrics.mdx +10 -0
  15. package/docs/authentication.mdx +25 -0
  16. package/docs/docs.json +35 -0
  17. package/docs/errors.mdx +34 -0
  18. package/docs/favicon.svg +5 -0
  19. package/docs/idempotency.mdx +18 -0
  20. package/docs/index.mdx +24 -0
  21. package/docs/quickstart.mdx +134 -0
  22. package/landing/DEPLOYING.md +33 -0
  23. package/landing/favicon.svg +5 -0
  24. package/landing/index.html +129 -0
  25. package/landing/main.js +45 -0
  26. package/landing/privacy.html +29 -0
  27. package/landing/styles.css +356 -0
  28. package/landing/terms.html +29 -0
  29. package/netlify.toml +15 -0
  30. package/openapi/openapi.yaml +1016 -0
  31. package/package.json +34 -0
  32. package/render.yaml +48 -0
  33. package/scripts/generate-sdk-py.sh +16 -0
  34. package/scripts/generate-sdk-ts.sh +16 -0
  35. package/scripts/migrate.js +66 -0
  36. package/scripts/validate-docs.js +56 -0
  37. package/scripts/validate-landing.js +39 -0
  38. package/sdks/python/README.md +40 -0
  39. package/sdks/python/emailagent_sdk/__init__.py +157 -0
  40. package/sdks/python/generated/.openapi-generator/FILES +101 -0
  41. package/sdks/python/generated/.openapi-generator/VERSION +1 -0
  42. package/sdks/python/generated/.openapi-generator-ignore +23 -0
  43. package/sdks/python/generated/emailagent_sdk_generated/__init__.py +105 -0
  44. package/sdks/python/generated/emailagent_sdk_generated/api/__init__.py +9 -0
  45. package/sdks/python/generated/emailagent_sdk_generated/api/api_keys_api.py +1162 -0
  46. package/sdks/python/generated/emailagent_sdk_generated/api/domains_api.py +1168 -0
  47. package/sdks/python/generated/emailagent_sdk_generated/api/emails_api.py +1232 -0
  48. package/sdks/python/generated/emailagent_sdk_generated/api/inboxes_api.py +1191 -0
  49. package/sdks/python/generated/emailagent_sdk_generated/api/metrics_api.py +285 -0
  50. package/sdks/python/generated/emailagent_sdk_generated/api_client.py +801 -0
  51. package/sdks/python/generated/emailagent_sdk_generated/api_response.py +21 -0
  52. package/sdks/python/generated/emailagent_sdk_generated/configuration.py +586 -0
  53. package/sdks/python/generated/emailagent_sdk_generated/docs/APIKeysApi.md +334 -0
  54. package/sdks/python/generated/emailagent_sdk_generated/docs/ApiKeyCreated.md +35 -0
  55. package/sdks/python/generated/emailagent_sdk_generated/docs/ApiKeyCreatedResponse.md +29 -0
  56. package/sdks/python/generated/emailagent_sdk_generated/docs/ApiKeyListItem.md +35 -0
  57. package/sdks/python/generated/emailagent_sdk_generated/docs/ApiKeyListResponse.md +29 -0
  58. package/sdks/python/generated/emailagent_sdk_generated/docs/CreateApiKeyRequest.md +30 -0
  59. package/sdks/python/generated/emailagent_sdk_generated/docs/CreateApiKeyRequestScopes.md +29 -0
  60. package/sdks/python/generated/emailagent_sdk_generated/docs/CreateDomainRequest.md +29 -0
  61. package/sdks/python/generated/emailagent_sdk_generated/docs/CreateInboxRequest.md +31 -0
  62. package/sdks/python/generated/emailagent_sdk_generated/docs/Domain.md +37 -0
  63. package/sdks/python/generated/emailagent_sdk_generated/docs/DomainListResponse.md +29 -0
  64. package/sdks/python/generated/emailagent_sdk_generated/docs/DomainResponse.md +29 -0
  65. package/sdks/python/generated/emailagent_sdk_generated/docs/DomainsApi.md +336 -0
  66. package/sdks/python/generated/emailagent_sdk_generated/docs/Email.md +43 -0
  67. package/sdks/python/generated/emailagent_sdk_generated/docs/EmailListResponse.md +29 -0
  68. package/sdks/python/generated/emailagent_sdk_generated/docs/EmailResponse.md +29 -0
  69. package/sdks/python/generated/emailagent_sdk_generated/docs/EmailsApi.md +353 -0
  70. package/sdks/python/generated/emailagent_sdk_generated/docs/ErrorResponse.md +29 -0
  71. package/sdks/python/generated/emailagent_sdk_generated/docs/Inbox.md +38 -0
  72. package/sdks/python/generated/emailagent_sdk_generated/docs/InboxListResponse.md +29 -0
  73. package/sdks/python/generated/emailagent_sdk_generated/docs/InboxResponse.md +29 -0
  74. package/sdks/python/generated/emailagent_sdk_generated/docs/InboxesApi.md +337 -0
  75. package/sdks/python/generated/emailagent_sdk_generated/docs/MetricsApi.md +83 -0
  76. package/sdks/python/generated/emailagent_sdk_generated/docs/MetricsData.md +35 -0
  77. package/sdks/python/generated/emailagent_sdk_generated/docs/MetricsResponse.md +29 -0
  78. package/sdks/python/generated/emailagent_sdk_generated/docs/OkResponse.md +29 -0
  79. package/sdks/python/generated/emailagent_sdk_generated/docs/PlanLimits.md +32 -0
  80. package/sdks/python/generated/emailagent_sdk_generated/docs/SendEmailRequest.md +32 -0
  81. package/sdks/python/generated/emailagent_sdk_generated/docs/UpdateEmailReadRequest.md +29 -0
  82. package/sdks/python/generated/emailagent_sdk_generated/docs/UpdateInboxRequest.md +29 -0
  83. package/sdks/python/generated/emailagent_sdk_generated/exceptions.py +216 -0
  84. package/sdks/python/generated/emailagent_sdk_generated/models/__init__.py +41 -0
  85. package/sdks/python/generated/emailagent_sdk_generated/models/api_key_created.py +113 -0
  86. package/sdks/python/generated/emailagent_sdk_generated/models/api_key_created_response.py +91 -0
  87. package/sdks/python/generated/emailagent_sdk_generated/models/api_key_list_item.py +123 -0
  88. package/sdks/python/generated/emailagent_sdk_generated/models/api_key_list_response.py +95 -0
  89. package/sdks/python/generated/emailagent_sdk_generated/models/create_api_key_request.py +93 -0
  90. package/sdks/python/generated/emailagent_sdk_generated/models/create_api_key_request_scopes.py +143 -0
  91. package/sdks/python/generated/emailagent_sdk_generated/models/create_domain_request.py +87 -0
  92. package/sdks/python/generated/emailagent_sdk_generated/models/create_inbox_request.py +91 -0
  93. package/sdks/python/generated/emailagent_sdk_generated/models/domain.py +134 -0
  94. package/sdks/python/generated/emailagent_sdk_generated/models/domain_list_response.py +95 -0
  95. package/sdks/python/generated/emailagent_sdk_generated/models/domain_response.py +91 -0
  96. package/sdks/python/generated/emailagent_sdk_generated/models/email.py +175 -0
  97. package/sdks/python/generated/emailagent_sdk_generated/models/email_list_response.py +95 -0
  98. package/sdks/python/generated/emailagent_sdk_generated/models/email_response.py +91 -0
  99. package/sdks/python/generated/emailagent_sdk_generated/models/error_response.py +87 -0
  100. package/sdks/python/generated/emailagent_sdk_generated/models/inbox.py +136 -0
  101. package/sdks/python/generated/emailagent_sdk_generated/models/inbox_list_response.py +95 -0
  102. package/sdks/python/generated/emailagent_sdk_generated/models/inbox_response.py +91 -0
  103. package/sdks/python/generated/emailagent_sdk_generated/models/metrics_data.py +110 -0
  104. package/sdks/python/generated/emailagent_sdk_generated/models/metrics_response.py +91 -0
  105. package/sdks/python/generated/emailagent_sdk_generated/models/ok_response.py +87 -0
  106. package/sdks/python/generated/emailagent_sdk_generated/models/plan_limits.py +93 -0
  107. package/sdks/python/generated/emailagent_sdk_generated/models/send_email_request.py +93 -0
  108. package/sdks/python/generated/emailagent_sdk_generated/models/update_email_read_request.py +87 -0
  109. package/sdks/python/generated/emailagent_sdk_generated/models/update_inbox_request.py +92 -0
  110. package/sdks/python/generated/emailagent_sdk_generated/rest.py +258 -0
  111. package/sdks/python/generated/emailagent_sdk_generated/test/__init__.py +0 -0
  112. package/sdks/python/generated/emailagent_sdk_generated/test/test_api_key_created.py +68 -0
  113. package/sdks/python/generated/emailagent_sdk_generated/test/test_api_key_created_response.py +52 -0
  114. package/sdks/python/generated/emailagent_sdk_generated/test/test_api_key_list_item.py +66 -0
  115. package/sdks/python/generated/emailagent_sdk_generated/test/test_api_key_list_response.py +56 -0
  116. package/sdks/python/generated/emailagent_sdk_generated/test/test_api_keys_api.py +59 -0
  117. package/sdks/python/generated/emailagent_sdk_generated/test/test_create_api_key_request.py +53 -0
  118. package/sdks/python/generated/emailagent_sdk_generated/test/test_create_api_key_request_scopes.py +50 -0
  119. package/sdks/python/generated/emailagent_sdk_generated/test/test_create_domain_request.py +52 -0
  120. package/sdks/python/generated/emailagent_sdk_generated/test/test_create_inbox_request.py +54 -0
  121. package/sdks/python/generated/emailagent_sdk_generated/test/test_domain.py +70 -0
  122. package/sdks/python/generated/emailagent_sdk_generated/test/test_domain_list_response.py +56 -0
  123. package/sdks/python/generated/emailagent_sdk_generated/test/test_domain_response.py +52 -0
  124. package/sdks/python/generated/emailagent_sdk_generated/test/test_domains_api.py +59 -0
  125. package/sdks/python/generated/emailagent_sdk_generated/test/test_email.py +79 -0
  126. package/sdks/python/generated/emailagent_sdk_generated/test/test_email_list_response.py +56 -0
  127. package/sdks/python/generated/emailagent_sdk_generated/test/test_email_response.py +52 -0
  128. package/sdks/python/generated/emailagent_sdk_generated/test/test_emails_api.py +59 -0
  129. package/sdks/python/generated/emailagent_sdk_generated/test/test_error_response.py +52 -0
  130. package/sdks/python/generated/emailagent_sdk_generated/test/test_inbox.py +68 -0
  131. package/sdks/python/generated/emailagent_sdk_generated/test/test_inbox_list_response.py +56 -0
  132. package/sdks/python/generated/emailagent_sdk_generated/test/test_inbox_response.py +52 -0
  133. package/sdks/python/generated/emailagent_sdk_generated/test/test_inboxes_api.py +59 -0
  134. package/sdks/python/generated/emailagent_sdk_generated/test/test_metrics_api.py +38 -0
  135. package/sdks/python/generated/emailagent_sdk_generated/test/test_metrics_data.py +72 -0
  136. package/sdks/python/generated/emailagent_sdk_generated/test/test_metrics_response.py +74 -0
  137. package/sdks/python/generated/emailagent_sdk_generated/test/test_ok_response.py +52 -0
  138. package/sdks/python/generated/emailagent_sdk_generated/test/test_plan_limits.py +58 -0
  139. package/sdks/python/generated/emailagent_sdk_generated/test/test_send_email_request.py +56 -0
  140. package/sdks/python/generated/emailagent_sdk_generated/test/test_update_email_read_request.py +52 -0
  141. package/sdks/python/generated/emailagent_sdk_generated/test/test_update_inbox_request.py +52 -0
  142. package/sdks/python/generated/emailagent_sdk_generated_README.md +140 -0
  143. package/sdks/python/openapitools.json +7 -0
  144. package/sdks/python/pyproject.toml +19 -0
  145. package/sdks/typescript/README.md +41 -0
  146. package/sdks/typescript/generated/.openapi-generator/FILES +41 -0
  147. package/sdks/typescript/generated/.openapi-generator/VERSION +1 -0
  148. package/sdks/typescript/generated/.openapi-generator-ignore +23 -0
  149. package/sdks/typescript/generated/package.json +21 -0
  150. package/sdks/typescript/generated/src/apis/APIKeysApi.ts +314 -0
  151. package/sdks/typescript/generated/src/apis/DomainsApi.ts +314 -0
  152. package/sdks/typescript/generated/src/apis/EmailsApi.ts +350 -0
  153. package/sdks/typescript/generated/src/apis/InboxesApi.ts +329 -0
  154. package/sdks/typescript/generated/src/apis/MetricsApi.ts +93 -0
  155. package/sdks/typescript/generated/src/apis/index.ts +7 -0
  156. package/sdks/typescript/generated/src/index.ts +5 -0
  157. package/sdks/typescript/generated/src/models/ApiKeyCreated.ts +123 -0
  158. package/sdks/typescript/generated/src/models/ApiKeyCreatedResponse.ts +74 -0
  159. package/sdks/typescript/generated/src/models/ApiKeyListItem.ts +121 -0
  160. package/sdks/typescript/generated/src/models/ApiKeyListResponse.ts +74 -0
  161. package/sdks/typescript/generated/src/models/CreateApiKeyRequest.ts +82 -0
  162. package/sdks/typescript/generated/src/models/CreateApiKeyRequestScopes.ts +45 -0
  163. package/sdks/typescript/generated/src/models/CreateDomainRequest.ts +66 -0
  164. package/sdks/typescript/generated/src/models/CreateInboxRequest.ts +82 -0
  165. package/sdks/typescript/generated/src/models/Domain.ts +152 -0
  166. package/sdks/typescript/generated/src/models/DomainListResponse.ts +74 -0
  167. package/sdks/typescript/generated/src/models/DomainResponse.ts +74 -0
  168. package/sdks/typescript/generated/src/models/Email.ts +222 -0
  169. package/sdks/typescript/generated/src/models/EmailListResponse.ts +74 -0
  170. package/sdks/typescript/generated/src/models/EmailResponse.ts +74 -0
  171. package/sdks/typescript/generated/src/models/ErrorResponse.ts +66 -0
  172. package/sdks/typescript/generated/src/models/Inbox.ts +159 -0
  173. package/sdks/typescript/generated/src/models/InboxListResponse.ts +74 -0
  174. package/sdks/typescript/generated/src/models/InboxResponse.ts +74 -0
  175. package/sdks/typescript/generated/src/models/MetricsData.ts +139 -0
  176. package/sdks/typescript/generated/src/models/MetricsResponse.ts +74 -0
  177. package/sdks/typescript/generated/src/models/OkResponse.ts +66 -0
  178. package/sdks/typescript/generated/src/models/PlanLimits.ts +93 -0
  179. package/sdks/typescript/generated/src/models/SendEmailRequest.ts +91 -0
  180. package/sdks/typescript/generated/src/models/UpdateEmailReadRequest.ts +66 -0
  181. package/sdks/typescript/generated/src/models/UpdateInboxRequest.ts +66 -0
  182. package/sdks/typescript/generated/src/models/index.ts +27 -0
  183. package/sdks/typescript/generated/src/runtime.ts +432 -0
  184. package/sdks/typescript/generated/tsconfig.esm.json +7 -0
  185. package/sdks/typescript/generated/tsconfig.json +16 -0
  186. package/sdks/typescript/openapitools.json +8 -0
  187. package/sdks/typescript/package.json +27 -0
  188. package/sdks/typescript/src/index.ts +138 -0
  189. package/sdks/typescript/tsconfig.json +14 -0
  190. package/sql/001_init.sql +143 -0
  191. package/sql/002_local_auth.sql +38 -0
  192. package/sql/003_domain_routes.sql +2 -0
  193. package/sql/004_reliability_primitives.sql +75 -0
  194. package/sql/005_auth_email_flows.sql +22 -0
  195. package/src/config.js +30 -0
  196. package/src/db.js +25 -0
  197. package/src/lib/api-auth.js +55 -0
  198. package/src/lib/auth.js +71 -0
  199. package/src/lib/csrf.js +46 -0
  200. package/src/lib/dodo.js +67 -0
  201. package/src/lib/email-templates.js +67 -0
  202. package/src/lib/idempotency.js +85 -0
  203. package/src/lib/mailgun.js +188 -0
  204. package/src/lib/plan.js +24 -0
  205. package/src/lib/rate-limit.js +43 -0
  206. package/src/lib/security.js +62 -0
  207. package/src/lib/session.js +21 -0
  208. package/src/lib/store.js +638 -0
  209. package/src/lib/transactional-mailer.js +54 -0
  210. package/src/lib/validation.js +30 -0
  211. package/src/routes/api.js +485 -0
  212. package/src/routes/app.js +699 -0
  213. package/src/routes/auth.js +404 -0
  214. package/src/routes/webhooks.js +257 -0
  215. package/src/server.js +79 -0
  216. package/src/views/pages/admin.ejs +58 -0
  217. package/src/views/pages/api-keys.ejs +56 -0
  218. package/src/views/pages/billing.ejs +71 -0
  219. package/src/views/pages/domains.ejs +106 -0
  220. package/src/views/pages/inboxes.ejs +127 -0
  221. package/src/views/pages/login.ejs +57 -0
  222. package/src/views/pages/metrics.ejs +34 -0
  223. package/src/views/pages/reset-password.ejs +19 -0
  224. package/src/views/partials/bottom.ejs +3 -0
  225. package/src/views/partials/csrf-field.ejs +3 -0
  226. package/src/views/partials/flash.ejs +3 -0
  227. package/src/views/partials/top.ejs +130 -0
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "agentmail-clone-v1",
3
+ "version": "0.1.0",
4
+ "description": "API-first inbox SaaS for agents (V1)",
5
+ "type": "module",
6
+ "main": "src/server.js",
7
+ "scripts": {
8
+ "dev": "node --watch src/server.js",
9
+ "landing:dev": "npx --yes serve@14.2.4 landing -l 8080",
10
+ "start": "node src/server.js",
11
+ "migrate": "node scripts/migrate.js",
12
+ "lint:openapi": "npx --yes @redocly/cli@1.24.0 lint openapi/openapi.yaml",
13
+ "docs:validate": "node scripts/validate-docs.js",
14
+ "landing:validate": "node scripts/validate-landing.js",
15
+ "generate:sdk:ts": "./scripts/generate-sdk-ts.sh",
16
+ "generate:sdk:py": "./scripts/generate-sdk-py.sh",
17
+ "generate:sdks": "npm run generate:sdk:ts && npm run generate:sdk:py",
18
+ "check:sdk:drift": "git diff --exit-code"
19
+ },
20
+ "dependencies": {
21
+ "@fastify/cookie": "^9.4.0",
22
+ "@fastify/formbody": "^7.4.0",
23
+ "@fastify/multipart": "^8.3.1",
24
+ "@fastify/session": "^10.9.0",
25
+ "@fastify/view": "^9.1.0",
26
+ "dodopayments": "^2.19.0",
27
+ "dotenv": "^16.4.5",
28
+ "ejs": "^3.1.10",
29
+ "fastify": "^4.28.1",
30
+ "fastify-raw-body": "^4.3.0",
31
+ "fastify-plugin": "^5.0.1",
32
+ "pg": "^8.13.1"
33
+ }
34
+ }
package/render.yaml ADDED
@@ -0,0 +1,48 @@
1
+ services:
2
+ - type: web
3
+ name: emailagent-app
4
+ runtime: node
5
+ plan: free
6
+ autoDeployTrigger: commit
7
+ healthCheckPath: /healthz
8
+ buildCommand: npm ci
9
+ startCommand: npm start
10
+ envVars:
11
+ - key: NODE_ENV
12
+ value: production
13
+ - key: NODE_VERSION
14
+ value: 22
15
+ - key: DATABASE_URL
16
+ fromDatabase:
17
+ name: emailagent-db
18
+ property: connectionString
19
+ - key: APP_BASE_URL
20
+ value: https://app.emailagent.dev
21
+ - key: APP_SHARED_DOMAIN
22
+ value: emailagent.dev
23
+ - key: ADMIN_EMAIL
24
+ value: jagan.ganti@gmail.com
25
+ - key: SESSION_SECRET
26
+ generateValue: true
27
+ - key: API_KEY_HASH_SECRET
28
+ generateValue: true
29
+ - key: MAILGUN_API_KEY
30
+ sync: false
31
+ - key: MAILGUN_REGION
32
+ value: us
33
+ - key: MAILGUN_WEBHOOK_SIGNING_KEY
34
+ sync: false
35
+ - key: MAILGUN_ACCOUNT_DOMAIN
36
+ value: emailagent.dev
37
+ - key: DODO_PAYMENTS_API_KEY
38
+ sync: false
39
+ - key: DODO_PAYMENTS_ENVIRONMENT
40
+ value: live_mode
41
+ - key: DODO_PAYMENTS_WEBHOOK_KEY
42
+ sync: false
43
+ - key: DODO_PAYMENTS_PRODUCT_ID_PAID
44
+ sync: false
45
+
46
+ databases:
47
+ - name: emailagent-db
48
+ plan: free
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ SPEC_PATH="$ROOT_DIR/openapi/openapi.yaml"
6
+ OUT_DIR="$ROOT_DIR/sdks/python/generated"
7
+ CONFIG_PATH="$ROOT_DIR/sdks/python/openapitools.json"
8
+
9
+ rm -rf "$OUT_DIR"
10
+ mkdir -p "$OUT_DIR"
11
+
12
+ npx --yes @openapitools/openapi-generator-cli@2.20.0 generate \
13
+ -g python \
14
+ -i "$SPEC_PATH" \
15
+ -o "$OUT_DIR" \
16
+ -c "$CONFIG_PATH"
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ SPEC_PATH="$ROOT_DIR/openapi/openapi.yaml"
6
+ OUT_DIR="$ROOT_DIR/sdks/typescript/generated"
7
+ CONFIG_PATH="$ROOT_DIR/sdks/typescript/openapitools.json"
8
+
9
+ rm -rf "$OUT_DIR"
10
+ mkdir -p "$OUT_DIR"
11
+
12
+ npx --yes @openapitools/openapi-generator-cli@2.20.0 generate \
13
+ -g typescript-fetch \
14
+ -i "$SPEC_PATH" \
15
+ -o "$OUT_DIR" \
16
+ -c "$CONFIG_PATH"
@@ -0,0 +1,66 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import dotenv from 'dotenv';
5
+ import pg from 'pg';
6
+
7
+ dotenv.config();
8
+
9
+ const { Pool } = pg;
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const migrationsDir = path.resolve(__dirname, '../sql');
13
+
14
+ async function run() {
15
+ if (!process.env.DATABASE_URL) {
16
+ throw new Error('DATABASE_URL is required');
17
+ }
18
+
19
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
20
+ const client = await pool.connect();
21
+
22
+ try {
23
+ await client.query('BEGIN');
24
+ await client.query(`
25
+ CREATE TABLE IF NOT EXISTS schema_migrations (
26
+ id BIGSERIAL PRIMARY KEY,
27
+ filename TEXT UNIQUE NOT NULL,
28
+ executed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
29
+ )
30
+ `);
31
+
32
+ const files = (await fs.readdir(migrationsDir))
33
+ .filter((f) => f.endsWith('.sql'))
34
+ .sort();
35
+
36
+ for (const filename of files) {
37
+ const already = await client.query(
38
+ 'SELECT 1 FROM schema_migrations WHERE filename = $1',
39
+ [filename]
40
+ );
41
+
42
+ if (already.rowCount > 0) {
43
+ continue;
44
+ }
45
+
46
+ const sql = await fs.readFile(path.join(migrationsDir, filename), 'utf8');
47
+ await client.query(sql);
48
+ await client.query('INSERT INTO schema_migrations (filename) VALUES ($1)', [filename]);
49
+ console.log(`Applied ${filename}`);
50
+ }
51
+
52
+ await client.query('COMMIT');
53
+ console.log('Migrations complete');
54
+ } catch (err) {
55
+ await client.query('ROLLBACK');
56
+ throw err;
57
+ } finally {
58
+ client.release();
59
+ await pool.end();
60
+ }
61
+ }
62
+
63
+ run().catch((err) => {
64
+ console.error(err);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,56 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const root = process.cwd();
5
+ const docsDir = path.join(root, 'docs');
6
+ const docsConfigPath = path.join(docsDir, 'docs.json');
7
+
8
+ if (!fs.existsSync(docsConfigPath)) {
9
+ console.error('Missing docs/docs.json');
10
+ process.exit(1);
11
+ }
12
+
13
+ const config = JSON.parse(fs.readFileSync(docsConfigPath, 'utf8'));
14
+ const missing = [];
15
+
16
+ function collectPages(node) {
17
+ if (!node) return [];
18
+ if (Array.isArray(node)) {
19
+ return node.flatMap(collectPages);
20
+ }
21
+ if (typeof node === 'string') {
22
+ return [node];
23
+ }
24
+ if (typeof node === 'object') {
25
+ if (Array.isArray(node.pages)) {
26
+ return node.pages.flatMap(collectPages);
27
+ }
28
+ return Object.values(node).flatMap(collectPages);
29
+ }
30
+ return [];
31
+ }
32
+
33
+ const pages = [...new Set(collectPages(config.navigation))];
34
+ for (const page of pages) {
35
+ if (page.startsWith('http://') || page.startsWith('https://')) {
36
+ continue;
37
+ }
38
+ const filePath = path.join(docsDir, `${page}.mdx`);
39
+ if (!fs.existsSync(filePath)) {
40
+ missing.push(filePath);
41
+ }
42
+ }
43
+
44
+ if (!fs.existsSync(path.join(root, 'openapi', 'openapi.yaml'))) {
45
+ missing.push(path.join(root, 'openapi', 'openapi.yaml'));
46
+ }
47
+
48
+ if (missing.length > 0) {
49
+ console.error('Docs validation failed. Missing files:');
50
+ for (const item of missing) {
51
+ console.error(`- ${item}`);
52
+ }
53
+ process.exit(1);
54
+ }
55
+
56
+ console.log(`Docs validation passed (${pages.length} pages found).`);
@@ -0,0 +1,39 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const root = process.cwd();
5
+ const landingDir = path.join(root, 'landing');
6
+ const requiredFiles = ['index.html', 'styles.css', 'main.js', 'privacy.html', 'terms.html', 'favicon.svg'];
7
+ const missing = requiredFiles.filter((file) => !fs.existsSync(path.join(landingDir, file)));
8
+
9
+ if (missing.length) {
10
+ console.error('Landing validation failed. Missing files:');
11
+ for (const file of missing) {
12
+ console.error(`- landing/${file}`);
13
+ }
14
+ process.exit(1);
15
+ }
16
+
17
+ const indexHtml = fs.readFileSync(path.join(landingDir, 'index.html'), 'utf8');
18
+
19
+ const requiredMarkers = [
20
+ 'Email infrastructure for AI agents',
21
+ 'https://app.emailagent.dev/',
22
+ 'https://docs.emailagent.dev',
23
+ 'data-cta="start-free-hero"',
24
+ 'data-cta="docs-hero"',
25
+ '/privacy.html',
26
+ '/terms.html',
27
+ 'canonical" href="https://emailagent.dev"'
28
+ ];
29
+
30
+ const missingMarkers = requiredMarkers.filter((marker) => !indexHtml.includes(marker));
31
+ if (missingMarkers.length) {
32
+ console.error('Landing validation failed. Missing required content markers:');
33
+ for (const marker of missingMarkers) {
34
+ console.error(`- ${marker}`);
35
+ }
36
+ process.exit(1);
37
+ }
38
+
39
+ console.log('Landing validation passed.');
@@ -0,0 +1,40 @@
1
+ # emailagent-sdk (Python)
2
+
3
+ Official Python SDK for EmailAgent.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install emailagent-sdk==0.1.0
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from emailagent_sdk import EmailAgentClient
15
+
16
+ client = EmailAgentClient(
17
+ api_key="<api_key>",
18
+ base_url="https://api.emailagent.dev",
19
+ )
20
+
21
+ print(client.list_inboxes())
22
+
23
+ emails = client.list_emails(inbox_id="<inbox_uuid>")
24
+ print(emails)
25
+
26
+ client.update_email_read(
27
+ email_id="<email_uuid>",
28
+ is_read=True,
29
+ idempotency_key="mark-read-001",
30
+ )
31
+ ```
32
+
33
+ ## Idempotency
34
+
35
+ ```python
36
+ client.create_inbox(
37
+ local_part="agent-1",
38
+ idempotency_key="create-inbox-agent-1",
39
+ )
40
+ ```
@@ -0,0 +1,157 @@
1
+ """Official Python SDK for EmailAgent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+ from urllib.parse import quote
8
+
9
+ import requests
10
+
11
+
12
+ @dataclass
13
+ class EmailAgentApiError(Exception):
14
+ status_code: int
15
+ payload: Any
16
+
17
+ def __str__(self) -> str:
18
+ if isinstance(self.payload, dict) and self.payload.get("error"):
19
+ return str(self.payload["error"])
20
+ return f"EmailAgent API error ({self.status_code})"
21
+
22
+
23
+ class EmailAgentClient:
24
+ def __init__(self, api_key: str, base_url: str = "https://api.emailagent.dev") -> None:
25
+ self.base_url = base_url.rstrip("/")
26
+ self.session = requests.Session()
27
+ self.session.headers.update({"Authorization": f"Bearer {api_key}"})
28
+
29
+ def _request(
30
+ self,
31
+ method: str,
32
+ path: str,
33
+ body: dict[str, Any] | None = None,
34
+ idempotency_key: str | None = None,
35
+ ) -> Any:
36
+ headers: dict[str, str] = {}
37
+ if idempotency_key:
38
+ headers["Idempotency-Key"] = idempotency_key
39
+
40
+ response = self.session.request(
41
+ method=method,
42
+ url=f"{self.base_url}{path}",
43
+ json=body,
44
+ headers=headers or None,
45
+ timeout=30,
46
+ )
47
+
48
+ payload: Any
49
+ try:
50
+ payload = response.json()
51
+ except ValueError:
52
+ payload = response.text
53
+
54
+ if response.status_code >= 400:
55
+ raise EmailAgentApiError(response.status_code, payload)
56
+
57
+ return payload
58
+
59
+ def list_inboxes(self) -> Any:
60
+ return self._request("GET", "/api/v1/inboxes")
61
+
62
+ def create_inbox(
63
+ self,
64
+ local_part: str,
65
+ display_name: str | None = None,
66
+ domain_name: str | None = None,
67
+ idempotency_key: str | None = None,
68
+ ) -> Any:
69
+ body: dict[str, Any] = {"localPart": local_part}
70
+ if display_name is not None:
71
+ body["displayName"] = display_name
72
+ if domain_name is not None:
73
+ body["domainName"] = domain_name
74
+ return self._request("POST", "/api/v1/inboxes", body, idempotency_key)
75
+
76
+ def update_inbox(self, inbox_id: str, display_name: str, idempotency_key: str | None = None) -> Any:
77
+ return self._request(
78
+ "PATCH",
79
+ f"/api/v1/inboxes/{inbox_id}",
80
+ {"displayName": display_name},
81
+ idempotency_key,
82
+ )
83
+
84
+ def delete_inbox(self, inbox_id: str, idempotency_key: str | None = None) -> Any:
85
+ return self._request("DELETE", f"/api/v1/inboxes/{inbox_id}", None, idempotency_key)
86
+
87
+ def send_email(
88
+ self,
89
+ inbox_id: str,
90
+ to: str,
91
+ subject: str | None = None,
92
+ text: str | None = None,
93
+ idempotency_key: str | None = None,
94
+ ) -> Any:
95
+ body: dict[str, Any] = {"inboxId": inbox_id, "to": to}
96
+ if subject is not None:
97
+ body["subject"] = subject
98
+ if text is not None:
99
+ body["text"] = text
100
+ return self._request("POST", "/api/v1/emails/send", body, idempotency_key)
101
+
102
+ def list_emails(self, inbox_id: str | None = None) -> Any:
103
+ path = "/api/v1/emails"
104
+ if inbox_id:
105
+ path = f"{path}?inboxId={quote(inbox_id, safe='')}"
106
+ return self._request("GET", path)
107
+
108
+ def update_email_read(
109
+ self,
110
+ email_id: str,
111
+ is_read: bool,
112
+ idempotency_key: str | None = None,
113
+ ) -> Any:
114
+ return self._request(
115
+ "PATCH",
116
+ f"/api/v1/emails/{email_id}/read",
117
+ {"isRead": is_read},
118
+ idempotency_key,
119
+ )
120
+
121
+ def delete_email(self, email_id: str, idempotency_key: str | None = None) -> Any:
122
+ return self._request("DELETE", f"/api/v1/emails/{email_id}", None, idempotency_key)
123
+
124
+ def list_domains(self) -> Any:
125
+ return self._request("GET", "/api/v1/domains")
126
+
127
+ def create_domain(self, name: str, idempotency_key: str | None = None) -> Any:
128
+ return self._request("POST", "/api/v1/domains", {"name": name}, idempotency_key)
129
+
130
+ def verify_domain(self, domain_id: str, idempotency_key: str | None = None) -> Any:
131
+ return self._request("POST", f"/api/v1/domains/{domain_id}/verify", None, idempotency_key)
132
+
133
+ def delete_domain(self, domain_id: str, idempotency_key: str | None = None) -> Any:
134
+ return self._request("DELETE", f"/api/v1/domains/{domain_id}", None, idempotency_key)
135
+
136
+ def list_api_keys(self) -> Any:
137
+ return self._request("GET", "/api/v1/api-keys")
138
+
139
+ def create_api_key(
140
+ self,
141
+ name: str,
142
+ scopes: list[str] | str | None = None,
143
+ idempotency_key: str | None = None,
144
+ ) -> Any:
145
+ body: dict[str, Any] = {"name": name}
146
+ if scopes is not None:
147
+ body["scopes"] = scopes
148
+ return self._request("POST", "/api/v1/api-keys", body, idempotency_key)
149
+
150
+ def rotate_api_key(self, api_key_id: str, idempotency_key: str | None = None) -> Any:
151
+ return self._request("POST", f"/api/v1/api-keys/{api_key_id}/rotate", None, idempotency_key)
152
+
153
+ def revoke_api_key(self, api_key_id: str, idempotency_key: str | None = None) -> Any:
154
+ return self._request("POST", f"/api/v1/api-keys/{api_key_id}/revoke", None, idempotency_key)
155
+
156
+ def get_metrics(self) -> Any:
157
+ return self._request("GET", "/api/v1/metrics")
@@ -0,0 +1,101 @@
1
+ .openapi-generator-ignore
2
+ emailagent_sdk_generated/__init__.py
3
+ emailagent_sdk_generated/api/__init__.py
4
+ emailagent_sdk_generated/api/api_keys_api.py
5
+ emailagent_sdk_generated/api/domains_api.py
6
+ emailagent_sdk_generated/api/emails_api.py
7
+ emailagent_sdk_generated/api/inboxes_api.py
8
+ emailagent_sdk_generated/api/metrics_api.py
9
+ emailagent_sdk_generated/api_client.py
10
+ emailagent_sdk_generated/api_response.py
11
+ emailagent_sdk_generated/configuration.py
12
+ emailagent_sdk_generated/docs/APIKeysApi.md
13
+ emailagent_sdk_generated/docs/ApiKeyCreated.md
14
+ emailagent_sdk_generated/docs/ApiKeyCreatedResponse.md
15
+ emailagent_sdk_generated/docs/ApiKeyListItem.md
16
+ emailagent_sdk_generated/docs/ApiKeyListResponse.md
17
+ emailagent_sdk_generated/docs/CreateApiKeyRequest.md
18
+ emailagent_sdk_generated/docs/CreateApiKeyRequestScopes.md
19
+ emailagent_sdk_generated/docs/CreateDomainRequest.md
20
+ emailagent_sdk_generated/docs/CreateInboxRequest.md
21
+ emailagent_sdk_generated/docs/Domain.md
22
+ emailagent_sdk_generated/docs/DomainListResponse.md
23
+ emailagent_sdk_generated/docs/DomainResponse.md
24
+ emailagent_sdk_generated/docs/DomainsApi.md
25
+ emailagent_sdk_generated/docs/Email.md
26
+ emailagent_sdk_generated/docs/EmailListResponse.md
27
+ emailagent_sdk_generated/docs/EmailResponse.md
28
+ emailagent_sdk_generated/docs/EmailsApi.md
29
+ emailagent_sdk_generated/docs/ErrorResponse.md
30
+ emailagent_sdk_generated/docs/Inbox.md
31
+ emailagent_sdk_generated/docs/InboxListResponse.md
32
+ emailagent_sdk_generated/docs/InboxResponse.md
33
+ emailagent_sdk_generated/docs/InboxesApi.md
34
+ emailagent_sdk_generated/docs/MetricsApi.md
35
+ emailagent_sdk_generated/docs/MetricsData.md
36
+ emailagent_sdk_generated/docs/MetricsResponse.md
37
+ emailagent_sdk_generated/docs/OkResponse.md
38
+ emailagent_sdk_generated/docs/PlanLimits.md
39
+ emailagent_sdk_generated/docs/SendEmailRequest.md
40
+ emailagent_sdk_generated/docs/UpdateEmailReadRequest.md
41
+ emailagent_sdk_generated/docs/UpdateInboxRequest.md
42
+ emailagent_sdk_generated/exceptions.py
43
+ emailagent_sdk_generated/models/__init__.py
44
+ emailagent_sdk_generated/models/api_key_created.py
45
+ emailagent_sdk_generated/models/api_key_created_response.py
46
+ emailagent_sdk_generated/models/api_key_list_item.py
47
+ emailagent_sdk_generated/models/api_key_list_response.py
48
+ emailagent_sdk_generated/models/create_api_key_request.py
49
+ emailagent_sdk_generated/models/create_api_key_request_scopes.py
50
+ emailagent_sdk_generated/models/create_domain_request.py
51
+ emailagent_sdk_generated/models/create_inbox_request.py
52
+ emailagent_sdk_generated/models/domain.py
53
+ emailagent_sdk_generated/models/domain_list_response.py
54
+ emailagent_sdk_generated/models/domain_response.py
55
+ emailagent_sdk_generated/models/email.py
56
+ emailagent_sdk_generated/models/email_list_response.py
57
+ emailagent_sdk_generated/models/email_response.py
58
+ emailagent_sdk_generated/models/error_response.py
59
+ emailagent_sdk_generated/models/inbox.py
60
+ emailagent_sdk_generated/models/inbox_list_response.py
61
+ emailagent_sdk_generated/models/inbox_response.py
62
+ emailagent_sdk_generated/models/metrics_data.py
63
+ emailagent_sdk_generated/models/metrics_response.py
64
+ emailagent_sdk_generated/models/ok_response.py
65
+ emailagent_sdk_generated/models/plan_limits.py
66
+ emailagent_sdk_generated/models/send_email_request.py
67
+ emailagent_sdk_generated/models/update_email_read_request.py
68
+ emailagent_sdk_generated/models/update_inbox_request.py
69
+ emailagent_sdk_generated/rest.py
70
+ emailagent_sdk_generated/test/__init__.py
71
+ emailagent_sdk_generated/test/test_api_key_created.py
72
+ emailagent_sdk_generated/test/test_api_key_created_response.py
73
+ emailagent_sdk_generated/test/test_api_key_list_item.py
74
+ emailagent_sdk_generated/test/test_api_key_list_response.py
75
+ emailagent_sdk_generated/test/test_api_keys_api.py
76
+ emailagent_sdk_generated/test/test_create_api_key_request.py
77
+ emailagent_sdk_generated/test/test_create_api_key_request_scopes.py
78
+ emailagent_sdk_generated/test/test_create_domain_request.py
79
+ emailagent_sdk_generated/test/test_create_inbox_request.py
80
+ emailagent_sdk_generated/test/test_domain.py
81
+ emailagent_sdk_generated/test/test_domain_list_response.py
82
+ emailagent_sdk_generated/test/test_domain_response.py
83
+ emailagent_sdk_generated/test/test_domains_api.py
84
+ emailagent_sdk_generated/test/test_email.py
85
+ emailagent_sdk_generated/test/test_email_list_response.py
86
+ emailagent_sdk_generated/test/test_email_response.py
87
+ emailagent_sdk_generated/test/test_emails_api.py
88
+ emailagent_sdk_generated/test/test_error_response.py
89
+ emailagent_sdk_generated/test/test_inbox.py
90
+ emailagent_sdk_generated/test/test_inbox_list_response.py
91
+ emailagent_sdk_generated/test/test_inbox_response.py
92
+ emailagent_sdk_generated/test/test_inboxes_api.py
93
+ emailagent_sdk_generated/test/test_metrics_api.py
94
+ emailagent_sdk_generated/test/test_metrics_data.py
95
+ emailagent_sdk_generated/test/test_metrics_response.py
96
+ emailagent_sdk_generated/test/test_ok_response.py
97
+ emailagent_sdk_generated/test/test_plan_limits.py
98
+ emailagent_sdk_generated/test/test_send_email_request.py
99
+ emailagent_sdk_generated/test/test_update_email_read_request.py
100
+ emailagent_sdk_generated/test/test_update_inbox_request.py
101
+ emailagent_sdk_generated_README.md
@@ -0,0 +1,23 @@
1
+ # OpenAPI Generator Ignore
2
+ # Generated by openapi-generator https://github.com/openapitools/openapi-generator
3
+
4
+ # Use this file to prevent files from being overwritten by the generator.
5
+ # The patterns follow closely to .gitignore or .dockerignore.
6
+
7
+ # As an example, the C# client generator defines ApiClient.cs.
8
+ # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9
+ #ApiClient.cs
10
+
11
+ # You can match any string of characters against a directory, file or extension with a single asterisk (*):
12
+ #foo/*/qux
13
+ # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14
+
15
+ # You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16
+ #foo/**/qux
17
+ # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18
+
19
+ # You can also negate patterns with an exclamation (!).
20
+ # For example, you can ignore all files in a docs folder with the file extension .md:
21
+ #docs/*.md
22
+ # Then explicitly reverse the ignore rule for a single file:
23
+ #!docs/README.md