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
4 changes: 4 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export MAILTRAP_ACCOUNT_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_id"
export MAILTRAP_ORGANIZATION_ID="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_id"
export MAILTRAP_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/account_api_token"
export MAILTRAP_ORGANIZATION_API_KEY="op://Mailtrap Dev/Mailtrap SDK Dev API Key/organization_api_token"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ dist
node_modules
yarn-error.log
coverage
.idea/
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Email API:
- Sending domain management CRUD – [`sending-domains/everything.ts`](examples/sending-domains/everything.ts)
- Sending stats (aggregated and by domain, category, ESP, date) – [`stats/everything.ts`](examples/stats/everything.ts)
- Email logs (list with filters, get by message ID) – [`email-logs/everything.ts`](examples/email-logs/everything.ts)
- Webhooks CRUD – [`webhooks/everything.ts`](examples/webhooks/everything.ts)

Email Sandbox (Testing):

Expand Down Expand Up @@ -265,6 +266,8 @@ General API:
- Accounts info – [`general/accounts.ts`](examples/general/accounts.ts)
- Permissions listing – [`general/permissions.ts`](examples/general/permissions.ts)
- Users listing – [`general/account-accesses.ts`](examples/general/account-accesses.ts)
- API tokens CRUD & reset – [`general/api-tokens.ts`](examples/general/api-tokens.ts)
- Sub-accounts (list & create) – [`sub-accounts/everything.ts`](examples/sub-accounts/everything.ts)

## Contributing

Expand Down
51 changes: 51 additions & 0 deletions examples/general/api-tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>";

const client = new MailtrapClient({
token: TOKEN,
accountId: Number(ACCOUNT_ID),
});

const apiTokensClient = client.general.apiTokens;

async function apiTokensFlow() {
try {
// List all API tokens visible to the current token. The full token value
// is never returned here — only `last_4_digits`.
const all = await apiTokensClient.getList();
console.log("All API tokens:", JSON.stringify(all, null, 2));

// Create a new API token scoped to specific resources.
// The full `token` value is returned only in this response — store it securely.
const created = await apiTokensClient.create({
name: "My token",
resources: [
{ resource_type: "account", resource_id: Number(ACCOUNT_ID), access_level: 10 },
],
});
console.log("Created API token:", JSON.stringify(created, null, 2));
console.log("Token value (store securely):", created.token);

const tokenId = created.id;

// Get a single API token by ID (full token value is not included)
const one = await apiTokensClient.get(tokenId);
console.log("One API token:", JSON.stringify(one, null, 2));

// Reset the API token: expires the existing token and returns a new one
// with the same permissions. The new `token` value is only returned here.
const reset = await apiTokensClient.reset(tokenId);
console.log("Reset API token:", JSON.stringify(reset, null, 2));
console.log("New token value (store securely):", reset.token);

// Permanently delete the API token
await apiTokensClient.delete(tokenId);
console.log("API token deleted");
} catch (error) {
console.error("Error in apiTokensFlow:", error instanceof Error ? error.message : String(error));
}
}

apiTokensFlow();
30 changes: 30 additions & 0 deletions examples/sub-accounts/everything.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const ORGANIZATION_ID = "<YOUR-ORGANIZATION-ID-HERE>";

const client = new MailtrapClient({
token: TOKEN,
organizationId: Number(ORGANIZATION_ID),
});

const subAccountsClient = client.organizations.subAccounts;

async function subAccountsFlow() {
try {
// List sub accounts under the organization.
// Requires sub-account management permissions on the token.
const all = await subAccountsClient.getList();
console.log("All sub accounts:", JSON.stringify(all, null, 2));

// Create a new sub account under the organization
const created = await subAccountsClient.create({
name: "Acme Marketing",
});
console.log("Created sub account:", JSON.stringify(created, null, 2));
} catch (error) {
console.error("Error in subAccountsFlow:", error instanceof Error ? error.message : String(error));
}
}

subAccountsFlow();
53 changes: 53 additions & 0 deletions examples/webhooks/everything.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MailtrapClient } from "mailtrap";

const TOKEN = "<YOUR-TOKEN-HERE>";
const ACCOUNT_ID = "<YOUR-ACCOUNT-ID-HERE>";

const client = new MailtrapClient({
token: TOKEN,
accountId: Number(ACCOUNT_ID),
});

async function webhooksFlow() {
try {
// List all webhooks for the account
const all = await client.webhooks.getList();
console.log("All webhooks:", JSON.stringify(all, null, 2));

// Create a new webhook. `signing_secret` is only returned here — store it
// securely; it is used to verify webhook payload signatures.
const created = await client.webhooks.create({
url: "https://example.com/webhooks/mailtrap",
webhook_type: "email_sending",
active: true,
payload_format: "json",
sending_stream: "transactional",
event_types: ["delivery", "bounce", "open", "click", "unsubscribe"],
});
console.log("Created webhook:", JSON.stringify(created, null, 2));
console.log("Signing secret (store securely):", created.data.signing_secret);

const webhookId = created.data.id;

// Get a single webhook by ID (signing_secret is NOT returned here)
const one = await client.webhooks.get(webhookId);
console.log("One webhook:", JSON.stringify(one, null, 2));

// Update the webhook. Only url, active, payload_format and event_types
// are mutable; webhook_type/sending_stream/domain_id are immutable.
const updated = await client.webhooks.update(webhookId, {
url: "https://example.com/webhooks/mailtrap/v2",
active: false,
event_types: ["delivery", "bounce"],
});
console.log("Updated webhook:", JSON.stringify(updated, null, 2));

// Delete the webhook
const deleted = await client.webhooks.delete(webhookId);
console.log("Deleted webhook:", JSON.stringify(deleted, null, 2));
} catch (error) {
console.error("Error in webhooksFlow:", error instanceof Error ? error.message : String(error));
}
}

webhooksFlow();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add promise rejection handler to prevent unhandled rejection.

The async function is invoked without await or .catch(), which can lead to unhandled promise rejections if an error escapes the internal try/catch block (e.g., during client initialization). In Node.js 15+, unhandled rejections may terminate the process.

🛡️ Proposed fix
-webhooksFlow();
+webhooksFlow().catch((err) => {
+  console.error("Unhandled error:", err);
+  process.exit(1);
+});

Alternatively, if targeting Node.js 14.8+, use top-level await:

-webhooksFlow();
+await webhooksFlow();
📝 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
webhooksFlow();
webhooksFlow().catch((err) => {
console.error("Unhandled error:", err);
process.exit(1);
});
🤖 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 `@examples/webhooks/everything.ts` at line 53, The call to webhooksFlow() is
invoked without handling its returned promise which can cause unhandled promise
rejections; update the invocation to either await webhooksFlow() (if using
top-level await) or append a rejection handler like webhooksFlow().catch(err =>
{ /* log and exit or handle error */ }) so any thrown error (e.g., in
webhooksFlow or client initialization) is caught and logged/handled; locate the
webhooksFlow invocation and replace it with one of these safe patterns and
ensure the handler uses the existing logger or process.exit as appropriate.

11 changes: 11 additions & 0 deletions src/__tests__/lib/api/General.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ describe("lib/api/General: ", () => {
expect(generalAPI).toHaveProperty("accounts");
expect(generalAPI).toHaveProperty("permissions");
expect(generalAPI).toHaveProperty("billing");
expect(generalAPI).toHaveProperty("apiTokens");
});

it("lazily instantiates account-specific APIs via getters when accountId is provided.", () => {
expect(generalAPI.accountAccesses).toBeDefined();
expect(generalAPI.permissions).toBeDefined();
expect(generalAPI.billing).toBeDefined();
expect(generalAPI.accounts).toBeDefined();
expect(generalAPI.apiTokens).toBeDefined();
});
});

Expand Down Expand Up @@ -67,6 +69,15 @@ describe("lib/api/General: ", () => {
"Account ID is required for this operation. Please provide accountId when creating GeneralAPI instance."
);
});

it("throws error when accessing apiTokens without accountId.", () => {
expect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
generalAPI.apiTokens;
}).toThrow(
"Account ID is required for this operation. Please provide accountId when creating GeneralAPI instance."
);
});
});

describe("account discovery functionality: ", () => {
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/lib/api/Organizations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import axios from "axios";

import OrganizationsBaseAPI from "../../../lib/api/Organizations";

describe("lib/api/Organizations: ", () => {
const organizationId = 1001;
const organizationsAPI = new OrganizationsBaseAPI(axios, organizationId);

describe("class OrganizationsBaseAPI(): ", () => {
describe("init: ", () => {
it("exposes subAccounts resource.", () => {
expect(organizationsAPI).toHaveProperty("subAccounts");
expect(typeof organizationsAPI.subAccounts.getList).toBe("function");
expect(typeof organizationsAPI.subAccounts.create).toBe("function");
});
});
});
});
20 changes: 20 additions & 0 deletions src/__tests__/lib/api/Webhooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import axios from "axios";

import WebhooksBaseAPI from "../../../lib/api/Webhooks";

describe("lib/api/Webhooks: ", () => {
const accountId = 100;
const webhooksAPI = new WebhooksBaseAPI(axios, accountId);

describe("class WebhooksBaseAPI(): ", () => {
describe("init: ", () => {
it("initializes with all necessary params.", () => {
expect(webhooksAPI).toHaveProperty("getList");
expect(webhooksAPI).toHaveProperty("create");
expect(webhooksAPI).toHaveProperty("get");
expect(webhooksAPI).toHaveProperty("update");
expect(webhooksAPI).toHaveProperty("delete");
});
});
});
});
Loading
Loading