RocketChat / RocketChat/Rocket.Chat

fix: REST API POST /api/v1/sendInvitationEmail always returns success: false

Open Beginner friendly
#41,997 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: bug
Dominant language
TypeScript
Stars
46.1k
Forks
13.9k
Avg merge
3d 3h
Merged PRs (30d)
130

Description

Description:

When invoking the REST API endpoint POST /api/v1/sendInvitationEmail, the endpoint constructs a response payload containing { "success": false } despite successfully delivering the invitation emails and incrementing the Invitation_Email_Count database metric.

This causes a direct violation of the endpoint's compiled AJV validation contract (sendInvitationEmailResponseSchema specifies success: { type: 'boolean', enum: [true] }), and misinforms API clients / SDK integrations that the invite request failed.


Steps to reproduce:
  1. Obtain an API authentication token for an account with the bulk-register-user permission.
  2. Send a POST request to /api/v1/sendInvitationEmail with a list of destination email addresses:
curl -X POST "http://localhost:3000/api/v1/sendInvitationEmail" \
  -H "X-Auth-Token: <AUTH_TOKEN>" \
  -H "X-User-Id: <USER_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": ["colleague@example.com"]
  }'
  1. Inspect the JSON response body and the server logs.

Expected behavior:
  • The server sends the invitation email via SMTP/Mailer.
  • The server increments the Invitation_Email_Count setting.
  • The API responds with HTTP 200 and the following payload:
    {
      "success": true
    }
    
    matching the sendInvitationEmailResponseSchema definition (enum: [true]).

Actual behavior:
  • The server sends the invitation email and increments Invitation_Email_Count.
  • However, the response payload is constructed as:
    {
      "success": false
    }
    
    falsely indicating that the operation failed.

Deep Root Cause & Code Analysis:
1. Missing Return Statement in Service Layer

In apps/meteor/server/lib/rooms/invites/sendInvitationEmail.ts:

export const sendInvitationEmail = async (userId: string, emails: string[]) => {
    check(emails, [String]);
    // ... validation ...
    for (const email of validEmails) {
        try {
            await Mailer.send({ ... });
            const value = await Settings.incrementValueById('Invitation_Email_Count', 1, { returnDocument: 'after' });
            if (value) {
                void notifyOnSettingChanged(value);
            }
            continue;
        } catch ({ message }: any) {
            throw new Meteor.Error('error-email-send-failed', ...);
        }
    }
    // ⚠️ Missing `return true;` -> async function implicitly returns Promise<undefined>
};
2. False-Evaluation in Route Handler

In apps/meteor/server/api/v1/invites.ts:

async function action() {
    const { emails } = this.bodyParams;
    try {
        // ⚠️ Boolean(await sendInvitationEmail(...)) === Boolean(undefined) === false
        return API.v1.success({ success: Boolean(await sendInvitationEmail(this.userId, emails)) });
    } catch (e: unknown) {
        return API.v1.failure({ error: e instanceof Error ? e.message : String(e) });
    }
}
3. Violation of AJV Response Schema

In apps/meteor/server/api/v1/invites.ts:

const sendInvitationEmailResponseSchema = ajv.compile<{ success: boolean }>({
    type: 'object',
    properties: {
        success: { type: 'boolean', enum: [true] },
    },
    required: ['success'],
    additionalProperties: false,
});

The compiled schema explicitly requires enum: [true], but the runtime evaluation produces { success: false }.


Proposed Fix:
1. In apps/meteor/server/lib/rooms/invites/sendInvitationEmail.ts:

Add explicit return type and return statement:

-export const sendInvitationEmail = async (userId: string, emails: string[]) => {
+export const sendInvitationEmail = async (userId: string, emails: string[]): Promise<boolean> => {
 	check(emails, [String]);
 	// ...
 	for (const email of validEmails) {
 		// ...
 	}
+	return true;
 };
2. In apps/meteor/tests/end-to-end/api/invites.ts:

Add automated test coverage:

describe('POST [/sendInvitationEmail]', () => {
    it('should send invitation emails and return success: true', async () => {
        const res = await request
            .post(api('sendInvitationEmail'))
            .set(credentials)
            .send({
                emails: ['test-invite-user@example.com'],
            })
            .expect(200);

        expect(res.body).to.have.property('success', true);
    });
});

Server Setup Information:
  • Version of Rocket.Chat Server: develop (latest 7.x / 8.x)
  • Deployment Method: Source / Docker / Kubernetes
  • NodeJS Version: 20.x / 22.x
  • MongoDB Version: 6.x / 7.x
  • DB Replicaset Oplog: Enabled

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/meteor/server/lib/rooms/invites/sendInvitationEmail.ts and apps/meteor/server/api/v1/invites.ts, then review the existing endpoint tests in apps/meteor/tests/end-to-end/api/invites.ts. Verify the service result is propagated and the POST /sendInvitationEmail response is success: true; add or run coverage for the successful invitation flow.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.