Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ SMTP_USER=test_userdadad@example.com # Example SMTP user


AWS_DEFAULT_REGION="us-east-1"
AWS_SECRET_KEY="some-secret-key"
AWS_ACCESS_KEY="some-access-key"
AWS_ACCESS_KEY_ID="some-access-key"
AWS_SECRET_ACCESS_KEY="some-secret-key"
AWS_SES_ENDPOINT="http://localhost:3003/api/ses"
AWS_SNS_ENDPOINT="http://localhost:3003/api/sns"

Expand Down
8 changes: 5 additions & 3 deletions .env.selfhost.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ GITHUB_SECRET="<your-github-client-secret>"
GOOGLE_CLIENT_ID="<your-google-client-id>"
GOOGLE_CLIENT_SECRET="<your-google-client-secret>"

# AWS details - required
# AWS details
# Provide static credentials OR rely on the AWS default credential chain
# (IAM role, ECS task role, instance profile, etc.) by omitting these vars.
AWS_DEFAULT_REGION="us-east-1"
AWS_SECRET_KEY="<your-aws-secret-key>"
AWS_ACCESS_KEY="<your-aws-access-key>"
AWS_ACCESS_KEY_ID="<your-aws-access-key-id>"
AWS_SECRET_ACCESS_KEY="<your-aws-secret-access-key>"



Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
NEXTAUTH_SECRET: test-secret
DATABASE_URL: postgresql://usesend:password@127.0.0.1:5432/usesend_test
REDIS_URL: redis://127.0.0.1:6379/15
AWS_ACCESS_KEY: test-access-key
AWS_SECRET_KEY: test-secret-key
AWS_ACCESS_KEY_ID: test-access-key
AWS_SECRET_ACCESS_KEY: test-secret-key
AWS_DEFAULT_REGION: us-east-1
NEXT_PUBLIC_IS_CLOUD: "true"
API_RATE_LIMIT: "2"
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ GITHUB_SECRET=your_client_secret
If you want to send real emails, add:

```env
AWS_ACCESS_KEY=your_access_key
AWS_SECRET_KEY=your_secret_key
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
```

> You can skip this by using the `local-sen-sns` image for local-only email development.
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/get-started/create-aws-credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ description: Step by step guide to create AWS credentials to self-host useSend.
Copy the access key ID and secret access key to your `.env` file.

```env
AWS_ACCESS_KEY=<access-key-id>
AWS_SECRET_KEY=<secret-access-key>
AWS_ACCESS_KEY_ID=<access-key-id>
AWS_SECRET_ACCESS_KEY=<secret-access-key>
```

![create access key](/images/aws/key-6.png)
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/get-started/local.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ Once the app is added you can add the Client ID under `GITHUB_ID`and CLIENT SECR
Next, we need to add in the [AWS credentials](https://docs.usesend.com/get-started/create-aws-credentials). Follow the detailed guide to get the AWS credentials with accurate permissions and add them in:

```
AWS_ACCESS_KEY=<access-key-id>
AWS_SECRET_KEY=<secret-access-key>
AWS_ACCESS_KEY_ID=<access-key-id>
AWS_SECRET_ACCESS_KEY=<secret-access-key>
```

</Step>
Expand Down
20 changes: 10 additions & 10 deletions apps/docs/get-started/set-up-docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ docker pull ghcr.io/usesend/usesend
```
docker run -d \
-p 3000:3000 \
-e NEXTAUTH_URL="<your-nextauth-url>"
-e NEXTAUTH_SECRET="<your-nextauth-secret>"
-e DATABASE_URL="<your-next-private-database-url>"
-e REDIS_URL="<your-next-private-redis-url>"
-e AWS_ACCESS_KEY="<your-next-private-aws-access-key-id>"
-e AWS_SECRET_KEY="<your-next-private-aws-secret-access-key>"
-e AWS_DEFAULT_REGION="<your-next-private-aws-region>"
-e GITHUB_ID="<your-next-private-github-id>"
-e GITHUB_SECRET="<your-next-private-github-secret>"
-e NEXTAUTH_URL="<your-nextauth-url>" \
-e NEXTAUTH_SECRET="<your-nextauth-secret>" \
-e DATABASE_URL="<your-next-private-database-url>" \
-e REDIS_URL="<your-next-private-redis-url>" \
-e AWS_ACCESS_KEY_ID="<your-next-private-aws-access-key-id>" \
-e AWS_SECRET_ACCESS_KEY="<your-next-private-aws-secret-access-key>" \
-e AWS_DEFAULT_REGION="<your-next-private-aws-region>" \
-e GITHUB_ID="<your-next-private-github-id>" \
-e GITHUB_SECRET="<your-next-private-github-secret>" \
usesend/usesend
```

Replace the placeholders with your actual database and aws details.
Replace the placeholders with your actual database and AWS details.

1. Access the useSend application by visiting the URL you provided in the `NEXTAUTH_URL` environment variable in your web browser.

Expand Down
4 changes: 2 additions & 2 deletions apps/docs/self-hosting/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ useSend depends on AWS SES to send emails and SNS to receive email status. Along
Add the following environment variables.

```env
AWS_ACCESS_KEY=<access-key-id>
AWS_SECRET_KEY=<secret-access-key>
AWS_ACCESS_KEY_ID=<access-key-id>
AWS_SECRET_ACCESS_KEY=<secret-access-key>
```

<Tip>
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/self-hosting/railway.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ useSend depends on AWS SES to send emails and SNS to receive email status. The R
Add the following environment variables in Railway.

```env
AWS_ACCESS_KEY=<access-key-id>
AWS_SECRET_KEY=<secret-access-key>
AWS_ACCESS_KEY_ID=<access-key-id>
AWS_SECRET_ACCESS_KEY=<secret-access-key>
```

<Tip>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/.env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ NEXTAUTH_SECRET=test-secret
DATABASE_URL=postgresql://usesend:password@127.0.0.1:54329/usesend_test
REDIS_URL=redis://127.0.0.1:6380/15

AWS_ACCESS_KEY=test-access-key
AWS_SECRET_KEY=test-secret-key
AWS_ACCESS_KEY_ID=test-access-key
AWS_SECRET_ACCESS_KEY=test-secret-key
AWS_DEFAULT_REGION=us-east-1

NEXT_PUBLIC_IS_CLOUD=true
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const env = createEnv({
),
GITHUB_ID: z.string().optional(),
GITHUB_SECRET: z.string().optional(),
AWS_ACCESS_KEY: z.string(),
AWS_SECRET_KEY: z.string(),
AWS_ACCESS_KEY_ID: z.string().optional(),
AWS_SECRET_ACCESS_KEY: z.string().optional(),
USESEND_API_KEY: z.string().optional(),
UNSEND_API_KEY: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
Expand Down Expand Up @@ -99,8 +99,8 @@ export const env = createEnv({
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
GITHUB_ID: process.env.GITHUB_ID,
GITHUB_SECRET: process.env.GITHUB_SECRET,
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_SECRET_KEY: process.env.AWS_SECRET_KEY,
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_KEY,
Comment on lines +102 to +103

@coderabbitai coderabbitai Bot May 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid mixing standard and legacy credential sources across fields.

Line 102 and Line 103 independently fall back with ||, which can combine AWS_ACCESS_KEY_ID with legacy AWS_SECRET_KEY (or vice versa). That can create an invalid pair and undercut partial-config safeguards.

Suggested fix
-    AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY,
-    AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_KEY,
+    AWS_ACCESS_KEY_ID:
+      process.env.AWS_ACCESS_KEY_ID ?? process.env.AWS_ACCESS_KEY,
+    AWS_SECRET_ACCESS_KEY:
+      process.env.AWS_ACCESS_KEY_ID != null ||
+      process.env.AWS_SECRET_ACCESS_KEY != null
+        ? process.env.AWS_SECRET_ACCESS_KEY
+        : process.env.AWS_SECRET_KEY,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY || process.env.AWS_SECRET_KEY,
AWS_ACCESS_KEY_ID:
process.env.AWS_ACCESS_KEY_ID ?? process.env.AWS_ACCESS_KEY,
AWS_SECRET_ACCESS_KEY:
process.env.AWS_ACCESS_KEY_ID != null ||
process.env.AWS_SECRET_ACCESS_KEY != null
? process.env.AWS_SECRET_ACCESS_KEY
: process.env.AWS_SECRET_KEY,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/env.js` around lines 102 - 103, The current fallback uses
independent || for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY which can mix
standard and legacy values; update the logic so the code selects either the
standard pair (AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY) or, only if both are
absent, the legacy pair (AWS_ACCESS_KEY + AWS_SECRET) as a unit. In practice
change the assignment for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to a small
conditional that prefers the standard env vars together, otherwise uses the
legacy pair only when both legacy vars exist, and yields undefined/null if
neither full pair is present; reference the AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY symbols when locating where to implement this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah thats fine, we should give responsibilty to users

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KMKoushik, understood — the independent fallback is intentional, and the partial-config guard in getAwsCredentialOptions in credentials.ts will still catch the case where only one standard key is set at runtime. Acknowledged!

(ᵔ ᵕ ᵔ) 🐇


✏️ Learnings added
Learnt from: KMKoushik
Repo: usesend/useSend PR: 401
File: apps/web/src/env.js:102-103
Timestamp: 2026-05-17T11:17:08.286Z
Learning: In `apps/web/src/env.js` (usesend/useSend), the independent `||` fallback for `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (falling back to legacy `AWS_ACCESS_KEY`/`AWS_SECRET_KEY` respectively) is intentional. The project maintainer explicitly chose to give users responsibility for providing consistent credential pairs rather than enforcing paired fallback logic at the env layer. The partial-config guard is handled at runtime in `apps/web/src/server/aws/credentials.ts` via `getAwsCredentialOptions()`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

USESEND_API_KEY: process.env.USESEND_API_KEY,
UNSEND_API_KEY: process.env.UNSEND_API_KEY,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
Expand Down
22 changes: 22 additions & 0 deletions apps/web/src/server/aws/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { env } from "~/env";

export function getAwsCredentialOptions() {
const hasKey = !!env.AWS_ACCESS_KEY_ID;
const hasSecret = !!env.AWS_SECRET_ACCESS_KEY;

if (hasKey !== hasSecret) {
throw new Error(
"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must both be set or both be omitted"
);
}

if (hasKey) {
return {
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID!,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY!,
},
};
}
return {};
}
11 changes: 3 additions & 8 deletions apps/web/src/server/aws/ses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import { generateKeyPairSync } from "crypto";
import nodemailer from "nodemailer";
import { env } from "~/env";
import { getAwsCredentialOptions } from "./credentials";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
import { EmailContent } from "~/types";
import { logger } from "../logger/log";
import { buildHeaders } from "~/server/utils/email-headers";
Expand All @@ -30,10 +31,7 @@ async function getAccountId(region: string) {

const stsClient = new STSClient({
region: region,
credentials: {
accessKeyId: env.AWS_ACCESS_KEY,
secretAccessKey: env.AWS_SECRET_KEY,
},
...getAwsCredentialOptions(),
});
const command = new GetCallerIdentityCommand({});
const response = await stsClient.send(command);
Expand All @@ -50,10 +48,7 @@ function getSesClient(region: string) {
return new SESv2Client({
region: region,
endpoint: env.AWS_SES_ENDPOINT,
credentials: {
accessKeyId: env.AWS_ACCESS_KEY,
secretAccessKey: env.AWS_SECRET_KEY,
},
...getAwsCredentialOptions(),
});
}

Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/server/aws/sns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import {
DeleteTopicCommand,
} from "@aws-sdk/client-sns";
import { env } from "~/env";
import { getAwsCredentialOptions } from "./credentials";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

function getSnsClient(region: string) {
return new SNSClient({
endpoint: env.AWS_SNS_ENDPOINT,
region: region,
credentials: {
accessKeyId: env.AWS_ACCESS_KEY,
secretAccessKey: env.AWS_SECRET_KEY,
},
...getAwsCredentialOptions(),
});
}

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/test/setup/setup-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const defaultEnv: Record<string, string> = {
NEXTAUTH_SECRET: "test-secret",
DATABASE_URL: "postgresql://usesend:password@127.0.0.1:54329/usesend_test",
REDIS_URL: "redis://127.0.0.1:6380/15",
AWS_ACCESS_KEY: "test-access-key",
AWS_SECRET_KEY: "test-secret-key",
AWS_ACCESS_KEY_ID: "test-access-key",
AWS_SECRET_ACCESS_KEY: "test-secret-key",
AWS_DEFAULT_REGION: "us-east-1",
NEXT_PUBLIC_IS_CLOUD: "true",
API_RATE_LIMIT: "2",
Expand Down
6 changes: 3 additions & 3 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ docker run -d \
-e NEXTAUTH_SECRET="<your-nextauth-secret>" \
-e DATABASE_URL="<your-database-url>" \
-e REDIS_URL="<your-redis-url>" \
-e AWS_ACCESS_KEY="<your-aws-access-key-id>" \
-e AWS_SECRET_KEY="<your-aws-secret-access-key>" \
-e AWS_ACCESS_KEY_ID="<your-aws-access-key-id>" \
-e AWS_SECRET_ACCESS_KEY="<your-aws-secret-access-key>" \
-e AWS_DEFAULT_REGION="<your-aws-region>" \
-e GITHUB_ID="<your-github-client-id>" \
-e GITHUB_SECRET="<your-github-client-secret>" \
usesend/usesend
```

Replace the placeholders with your actual database and aws details.
Replace the placeholders with your actual database and AWS details.

1. Access the useSend application by visiting the URL you provided in the `NEXTAUTH_URL` environment variable in your web browser.

Expand Down
6 changes: 4 additions & 2 deletions docker/prod/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ services:
- DATABASE_URL=${DATABASE_URL:?err}
- NEXTAUTH_URL=${NEXTAUTH_URL:?err}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?err}
- AWS_ACCESS_KEY=${AWS_ACCESS_KEY:?err}
- AWS_SECRET_KEY=${AWS_SECRET_KEY:?err}
- AWS_ACCESS_KEY=${AWS_ACCESS_KEY:-}
- AWS_SECRET_KEY=${AWS_SECRET_KEY:-}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
- AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:?err}
- GITHUB_ID=${GITHUB_ID:?err}
- GITHUB_SECRET=${GITHUB_SECRET:?err}
Expand Down
8 changes: 7 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@
"GITHUB_SECRET",
"AWS_SECRET_KEY",
"AWS_ACCESS_KEY",
"AWS_SECRET_ACCESS_KEY",
"AWS_ACCESS_KEY_ID",
"AWS_DEFAULT_REGION",
"AWS_SES_ENDPOINT",
"AWS_SNS_ENDPOINT",
"NEXTAUTH_SECRET",
"NODE_ENV",
"VERCEL_URL",
"VERCEL",
"SKIP_ENV_VALIDATION",
"DOCKER_OUTPUT",
"PORT",
"UNSEND_API_KEY",
"USESEND_API_KEY",
Expand Down Expand Up @@ -57,4 +63,4 @@
"cache": false
}
}
}
}
Loading