Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion app/api/emails/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function OPTIONS() {
* POST /api/emails
*
* Sends an email to one or more explicit recipients via Resend. Emails are sent
* from `Agent by Recoup <agent@recoupable.com>`. Account-scoped — requires
* from `Agent by Recoup <agent@recoupable.dev>`. Account-scoped — requires
* authentication via x-api-key header or Authorization Bearer token.
*
* Body parameters:
Expand Down
2 changes: 1 addition & 1 deletion lib/composio/toolRouter/getSharedAccountConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const SHARED_ACCOUNT_ID = "recoup-shared-767f498e-e1e9-43c6-a152-a96ae3bd8d07";
* Get Google Drive/Sheets/Docs connections from the shared Recoupable account.
*
* When a customer doesn't want to grant full Google Drive access,
* they can share specific files with shared@recoupable.com instead.
* they can share specific files with shared@recoupable.dev instead.
* This function fetches the shared account's connections so they
* can be used as a fallback in tool router sessions.
*
Expand Down
8 changes: 4 additions & 4 deletions lib/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export const PRIVY_PROJECT_SECRET = process.env.PRIVY_PROJECT_SECRET;
/** Base URL for the public API documentation site */
export const DOCS_BASE_URL = "https://docs.recoupable.dev";

/** Domain for receiving inbound emails (e.g., support@mail.recoupable.com) */
export const INBOUND_EMAIL_DOMAIN = "@mail.recoupable.com";
/** Domain for receiving inbound emails (e.g., support@recoupable.dev) */
export const INBOUND_EMAIL_DOMAIN = "@recoupable.dev";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: Custom agent: Flag AI Slop and Fabricated Changes

INBOUND_EMAIL_DOMAIN incorrectly set to @recoupable.dev instead of @mail.recoupable.dev, contradicting PR description and breaking inbound email routing

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/const.ts, line 19:

<comment>INBOUND_EMAIL_DOMAIN incorrectly set to @recoupable.dev instead of @mail.recoupable.dev, contradicting PR description and breaking inbound email routing</comment>

<file context>
@@ -15,8 +15,8 @@ export const PRIVY_PROJECT_SECRET = process.env.PRIVY_PROJECT_SECRET;
-/** Domain for receiving inbound emails (e.g., support@mail.recoupable.dev) */
-export const INBOUND_EMAIL_DOMAIN = "@mail.recoupable.dev";
+/** Domain for receiving inbound emails (e.g., support@recoupable.dev) */
+export const INBOUND_EMAIL_DOMAIN = "@recoupable.dev";
 
 /** Domain for sending outbound emails (e.g., support@recoupable.dev) */
</file context>


/** Domain for sending outbound emails (e.g., support@recoupable.com) */
export const OUTBOUND_EMAIL_DOMAIN = "@recoupable.com";
/** Domain for sending outbound emails (e.g., support@recoupable.dev) */
export const OUTBOUND_EMAIL_DOMAIN = "@recoupable.dev";

/** Default from address for outbound emails */
export const RECOUP_FROM_EMAIL = `Agent by Recoup <agent${OUTBOUND_EMAIL_DOMAIN}>`;
Expand Down
2 changes: 1 addition & 1 deletion lib/emails/__tests__/processAndSendEmail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("processAndSendEmail", () => {
}
expect(mockSendEmailWithResend).toHaveBeenCalledWith(
expect.objectContaining({
from: "Agent by Recoup <agent@recoupable.com>",
from: "Agent by Recoup <agent@recoupable.dev>",
to: ["user@example.com"],
subject: "Test",
html: expect.stringContaining("Hello world"),
Expand Down
45 changes: 21 additions & 24 deletions lib/emails/inbound/__tests__/getFromWithName.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,78 @@ import { getFromWithName } from "../getFromWithName";

describe("getFromWithName", () => {
describe("outbound domain conversion", () => {
it("converts inbound @mail.recoupable.com to outbound @recoupable.com", () => {
const result = getFromWithName(["support@mail.recoupable.com"]);
it("converts inbound @recoupable.dev to outbound @recoupable.dev", () => {
const result = getFromWithName(["support@recoupable.dev"]);

expect(result).toBe("Support by Recoup <support@recoupable.com>");
expect(result).toBe("Support by Recoup <support@recoupable.dev>");
});

it("preserves the email name when converting domains", () => {
const result = getFromWithName(["agent@mail.recoupable.com"]);
const result = getFromWithName(["agent@recoupable.dev"]);

expect(result).toBe("Agent by Recoup <agent@recoupable.com>");
expect(result).toBe("Agent by Recoup <agent@recoupable.dev>");
});
});

describe("finding inbound email", () => {
it("finds recoup email in to array", () => {
const result = getFromWithName(["hello@mail.recoupable.com"]);
const result = getFromWithName(["hello@recoupable.dev"]);

expect(result).toBe("Hello by Recoup <hello@recoupable.com>");
expect(result).toBe("Hello by Recoup <hello@recoupable.dev>");
});

it("finds recoup email among multiple to addresses", () => {
const result = getFromWithName([
"other@example.com",
"support@mail.recoupable.com",
"support@recoupable.dev",
"another@example.com",
]);

expect(result).toBe("Support by Recoup <support@recoupable.com>");
expect(result).toBe("Support by Recoup <support@recoupable.dev>");
});

it("falls back to cc array when not in to array", () => {
const result = getFromWithName(["other@example.com"], ["support@mail.recoupable.com"]);
const result = getFromWithName(["other@example.com"], ["support@recoupable.dev"]);

expect(result).toBe("Support by Recoup <support@recoupable.com>");
expect(result).toBe("Support by Recoup <support@recoupable.dev>");
});

it("prefers to array over cc array", () => {
const result = getFromWithName(
["to-agent@mail.recoupable.com"],
["cc-agent@mail.recoupable.com"],
);
const result = getFromWithName(["to-agent@recoupable.dev"], ["cc-agent@recoupable.dev"]);

expect(result).toBe("To-agent by Recoup <to-agent@recoupable.com>");
expect(result).toBe("To-agent by Recoup <to-agent@recoupable.dev>");
});

it("handles case-insensitive domain matching", () => {
const result = getFromWithName(["Support@MAIL.RECOUPABLE.COM"]);
const result = getFromWithName(["Support@RECOUPABLE.DEV"]);

expect(result).toBe("Support by Recoup <Support@recoupable.com>");
expect(result).toBe("Support by Recoup <Support@recoupable.dev>");
});
});

describe("error handling", () => {
it("throws error when no recoup email found in to or cc", () => {
expect(() => getFromWithName(["other@example.com"])).toThrow(
"No email found ending with @mail.recoupable.com",
"No email found ending with @recoupable.dev",
);
});

it("throws error when arrays are empty", () => {
expect(() => getFromWithName([])).toThrow("No email found ending with @mail.recoupable.com");
expect(() => getFromWithName([])).toThrow("No email found ending with @recoupable.dev");
});
});

describe("name formatting", () => {
it("capitalizes first letter of name", () => {
const result = getFromWithName(["lowercase@mail.recoupable.com"]);
const result = getFromWithName(["lowercase@recoupable.dev"]);

expect(result).toBe("Lowercase by Recoup <lowercase@recoupable.com>");
expect(result).toBe("Lowercase by Recoup <lowercase@recoupable.dev>");
});

it("preserves rest of name casing", () => {
const result = getFromWithName(["myAgent@mail.recoupable.com"]);
const result = getFromWithName(["myAgent@recoupable.dev"]);

expect(result).toBe("MyAgent by Recoup <myAgent@recoupable.com>");
expect(result).toBe("MyAgent by Recoup <myAgent@recoupable.dev>");
});
});
});
2 changes: 1 addition & 1 deletion lib/emails/inbound/getFromWithName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OUTBOUND_EMAIL_DOMAIN, INBOUND_EMAIL_DOMAIN } from "@/lib/const";
*
* @param toEmails - Array of email addresses from the 'to' field
* @param ccEmails - Optional array of email addresses from the 'cc' field (fallback)
* @returns Formatted email address with display name (e.g., "Support by Recoup <support@recoupable.com>")
* @returns Formatted email address with display name (e.g., "Support by Recoup <support@recoupable.dev>")
* @throws Error if no email ending with the inbound domain is found in either array
*/
export function getFromWithName(toEmails: string[], ccEmails: string[] = []): string {
Expand Down
2 changes: 1 addition & 1 deletion lib/emails/isTestEmail.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Returns true if the email is a test email
export const isTestEmail = (email: string): boolean => {
return email === "sweetmantech@gmail.com" || email === "sidney@recoupable.com";
return email === "sweetmantech@gmail.com" || email === "sidney@recoupable.dev";
};
2 changes: 1 addition & 1 deletion lib/emails/sendEmailHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { processAndSendEmail } from "@/lib/emails/processAndSendEmail";
* Handler for POST /api/emails.
*
* Sends an email to the explicit recipients in the request body via Resend
* (from `Agent by Recoup <agent@recoupable.com>`), reusing the same
* (from `Agent by Recoup <agent@recoupable.dev>`), reusing the same
* `processAndSendEmail` domain function as the `send_email` MCP tool.
* Account-scoped: requires authentication via x-api-key or Authorization Bearer.
* Body validation, auth, and the recipient restriction all live in
Expand Down
6 changes: 3 additions & 3 deletions lib/sandbox/__tests__/resolveGitUser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe("resolveGitUser", () => {
const gitUser = await resolveGitUser(ACCOUNT_ID);

expect(gitUser.name).toBe("Ada Lovelace");
expect(gitUser.email).toBe(`${ACCOUNT_ID}@users.noreply.recoupable.com`);
expect(gitUser.email).toBe(`${ACCOUNT_ID}@users.noreply.recoupable.dev`);
});

it("falls back on both fields when nothing is on file", async () => {
Expand All @@ -64,7 +64,7 @@ describe("resolveGitUser", () => {

expect(gitUser).toEqual({
name: `recoupable-${ACCOUNT_ID.slice(0, 8)}`,
email: `${ACCOUNT_ID}@users.noreply.recoupable.com`,
email: `${ACCOUNT_ID}@users.noreply.recoupable.dev`,
});
});

Expand All @@ -78,6 +78,6 @@ describe("resolveGitUser", () => {

const gitUser = await resolveGitUser(ACCOUNT_ID);

expect(gitUser.email).toBe(`${ACCOUNT_ID}@users.noreply.recoupable.com`);
expect(gitUser.email).toBe(`${ACCOUNT_ID}@users.noreply.recoupable.dev`);
});
});
2 changes: 1 addition & 1 deletion lib/sandbox/resolveGitUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function resolveGitUser(accountId: string): Promise<GitUser> {
const emailRow = emails.find(row => typeof row.email === "string" && row.email.length > 0);

const name = account?.name?.trim() || `recoupable-${accountId.slice(0, 8)}`;
const email = emailRow?.email ?? `${accountId}@users.noreply.recoupable.com`;
const email = emailRow?.email ?? `${accountId}@users.noreply.recoupable.dev`;

return { name, email };
}
Loading