mailpit-api
Version:
A TypeScript client for interacting with Mailpit's REST API.
539 lines (538 loc) • 18.8 kB
JavaScript
// src/index.ts
import axios, {
isAxiosError
} from "axios";
var MailpitClient = class {
axiosInstance;
/**
* Creates an instance of {@link MailpitClient}.
* @param baseURL - The base URL of the Mailpit API.
* @param auth - Optional authentication credentials.
* @param auth.username - The username for basic authentication.
* @param auth.password - The password for basic authentication.
* @example No Auth
* ```typescript
* const mailpit = new MailpitClient("http://localhost:8025");
* ```
* @example Basic Auth
* ```typescript
* const mailpit = new MailpitClient("http://localhost:8025", {
* username: "admin",
* password: "supersecret",
* });
* ```
*/
constructor(baseURL, auth) {
this.axiosInstance = axios.create({
baseURL,
auth,
validateStatus: function(status) {
return status === 200;
}
});
}
async handleRequest(request, options = { fullResponse: false }) {
try {
const response = await request();
return options.fullResponse ? response : response.data;
} catch (error) {
if (isAxiosError(error)) {
const url = error.config?.url || "UNKNOWN URL";
const method = error.config?.method?.toUpperCase() || "UNKNOWN METHOD";
if (error.response) {
throw new Error(
`Mailpit API Error: ${error.response.status.toString()} ${error.response.statusText} at ${method} ${url}: ${JSON.stringify(error.response.data)}`
);
} else if (error.request) {
throw new Error(
`Mailpit API Error: No response received from server at ${method} ${url}`
);
} else {
throw new Error(
`Mailpit API Error: ${error.toString()} at ${method} ${url}`
);
}
} else {
throw new Error(`Unexpected Error: ${error}`);
}
}
}
/**
* Retrieves information about the Mailpit instance.
*
* @returns Basic runtime information, message totals and latest release version.
* @example
* ```typescript
* const info = await mailpit.getInfo();
* ```
*/
async getInfo() {
return await this.handleRequest(
() => this.axiosInstance.get("/api/v1/info")
);
}
/**
* Retrieves the configuration of the Mailpit web UI.
* @remarks Intended for web UI only!
* @returns Configuration settings
* @example
* ```typescript
* const config = await mailpit.getConfiguration();
* ```
*/
async getConfiguration() {
return await this.handleRequest(
() => this.axiosInstance.get("/api/v1/webui")
);
}
/**
* Retrieves a summary of a specific message and marks it as read.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns Message summary
* @example
* ```typescript
* const message = await mailpit.getMessageSummary();
* ```
*/
async getMessageSummary(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}`
)
);
}
/**
* Retrieves the headers of a specific message.
* @remarks Header keys are returned alphabetically.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns Message headers
* @example
* ```typescript
* const headers = await mailpit.getMessageHeaders();
* ```
*/
async getMessageHeaders(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/headers`
)
);
}
/**
* Retrieves a specific attachment from a message.
* @param id - Message database ID or "latest"
* @param partID - The attachment part ID
* @returns Attachment as binary data and the content type
* @example
* ```typescript
* const message = await mailpit.getMessageSummary();
* if (message.Attachments.length) {
* const attachment = await mailpit.getMessageAttachment(message.ID, message.Attachments[0].PartID);
* // Do something with the attachment data
* }
* ```
*/
async getMessageAttachment(id, partID) {
const response = await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/part/${partID}`,
{ responseType: "arraybuffer" }
),
{ fullResponse: true }
);
return {
data: response.data,
contentType: response.headers["content-type"]
};
}
/**
* Generates a cropped 180x120 JPEG thumbnail of an image attachment from a message.
* Only image attachments are supported.
* @remarks
* If the image is smaller than 180x120 then the image is padded.
* If the attachment is not an image then a blank image is returned.
* @param id - Message database ID or "latest"
* @param partID - The attachment part ID
* @returns Image attachment thumbnail as binary data and the content type
* @example
* ```typescript
* const message = await mailpit.getMessageSummary();
* if (message.Attachments.length) {
* const thumbnail = await mailpit.getAttachmentThumbnail(message.ID, message.Attachments[0].PartID);
* // Do something with the thumbnail data
* }
* ```
*/
async getAttachmentThumbnail(id, partID) {
const response = await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/part/${partID}/thumb`,
{
responseType: "arraybuffer"
}
),
{ fullResponse: true }
);
return {
data: response.data,
contentType: response.headers["content-type"]
};
}
/**
* Retrieves the full email message source as plain text.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns Plain text message source
* @example
* ```typescript
* const messageSource = await mailpit.getMessageSource();
* ```
*/
async getMessageSource(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(`/api/v1/message/${id}/raw`)
);
}
/**
* Release a message via a pre-configured external SMTP server.
* @remarks This is only enabled if message relaying has been configured.
* @param id - The message database ID. Use `latest` to return the latest message.
* @param relayTo - Array of email addresses to relay the message to
* @returns Plain text "ok" response
* @example
* ```typescript
* const message = await mailpit.releaseMessage("latest", ["user1@example.test", "user2@example.test"]);
* ```
*/
async releaseMessage(id, relayTo) {
return await this.handleRequest(
() => this.axiosInstance.post(`/api/v1/message/${id}/release`, relayTo)
);
}
/**
* Sends a message
* @param sendReqest - The request containing the message details.
* @returns Response containing database messsage ID
* @example
* ```typescript
* await mailpit.sendMessage(
* From: { Email: "user@example.test", Name: "First LastName" },
* To: [{ Email: "rec@example.test", Name: "Recipient Name"}, {Email: "another@example.test"}],
* Subject: "Test Email",
* );
* ```
*/
async sendMessage(sendReqest) {
return await this.handleRequest(
() => this.axiosInstance.post(
`/api/v1/send`,
sendReqest
)
);
}
/**
* Retrieves a list of message summaries ordered from newest to oldest.
* @remarks Only contains the number of attachments and a snippet of the message body.
* @see {@link MailpitClient.getMessageSummary | getMessageSummary()} for more attachment and body details for a specific message.
* @param start - The pagination offset. Defaults to `0`.
* @param limit - The number of messages to retrieve. Defaults to `50`.
* @returns A list of message summaries
* @example
* ```typescript
* const messages = await.listMessages();
* ```
*/
async listMessages(start = 0, limit = 50) {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/messages`,
{ params: { start, limit } }
)
);
}
/**
* Set the read status of messages.
* @remarks You can optionally provide an array of `IDs` **OR** a `Search` filter. If neither is set then all messages are updated.
* @param readStatus - The request containing the message database IDs/search string and the read status.
* @param readStatus.Read - The read status to set. Defaults to `false`.
* @param readStatus.IDs - The optional IDs of the messages to update.
* @param readStatus.Search - The optional search string to filter messages.
* @param params - Optional parameters for defining the time zone when using the `before:` and `after:` search filters.
* @see {@link https://mailpit.axllent.org/docs/usage/search-filters/ | Search filters}
* @returns Plain text "ok" response
* @example
* ```typescript
* // Set all messages as unread
* await mailpit.setReadStatus();
*
* // Set all messages as read
* await mailpit.setReadStatus({ Read: true });
*
* // Set specific messages as read using IDs
* await mailpit.setReadStatus({ IDs: ["1", "2", "3"], Read: true });
*
* // Set specific messages as read using search
* await mailpit.setReadStatus({ Search: "from:example.test", Read: true });
*
* // Set specific messages as read using after: search with time zone
* await mailpit.setReadStatus({ Search: "after:2025-04-30", Read: true }, { tz: "America/Chicago" });
* ```
*/
async setReadStatus(readStatus, params) {
return await this.handleRequest(
() => this.axiosInstance.put(`/api/v1/messages`, readStatus, {
params
})
);
}
/**
* Delete individual or all messages.
* @remarks If no `IDs` are provided then all messages are deleted.
* @param deleteRequest - The request containing the message database IDs to delete.
* @returns Plain text "ok" response
* @example
* ```typescript
* // Delete all messages
* await mailpit.deleteMessages();
*
* // Delete specific messages
* await mailpit.deleteMessages({ IDs: ["1", "2", "3"] });
* ```
*/
async deleteMessages(deleteRequest) {
return await this.handleRequest(
() => this.axiosInstance.delete(`/api/v1/messages`, {
data: deleteRequest
})
);
}
/**
* Retrieve messages matching a search, sorted by received date (descending).
* @see {@link https://mailpit.axllent.org/docs/usage/search-filters/ | Search filters}
* @remarks Only contains the number of attachments and a snippet of the message body.
* @see {@link MailpitClient.getMessageSummary | getMessageSummary()} for more attachment and body details for a specific message.
* @param search - The search request containing the query and optional parameters.
* @returns A list of message summaries matching the search criteria.
* @example
* ```typescript
* // Search for messages from a the domain example.test
* const messages = await mailpit.searchMessages({query: "from:example.test"});
* ```
*/
async searchMessages(search) {
return await this.handleRequest(
() => this.axiosInstance.get(`/api/v1/search`, {
params: search
})
);
}
/**
* Delete all messages matching a search.
* @see {@link https://mailpit.axllent.org/docs/usage/search-filters/ | Search filters}
* @param search - The search request containing the query.
* @returns Plain text "ok" response
* @example
* ```typescript
* // Delete all messages from the domain example.test
* await mailpit.deleteMessagesBySearch({query: "from:example.test"});
* ```
*/
async deleteMessagesBySearch(search) {
return await this.handleRequest(
() => this.axiosInstance.delete(`/api/v1/search`, { params: search })
);
}
/**
* Performs an HTML check on a specific message.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns The summary of the message HTML checker
* @example
* ```typescript
* const htmlCheck = await mailpit.htmlCheck();
* ```
*/
async htmlCheck(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/html-check`
)
);
}
/**
* Performs a link check on a specific message.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @param follow - Whether to follow links. Defaults to `false`.
* @returns The summary of the message Link checker.
* @example
* ```typescript
* const linkCheck = await mailpit.linkCheck();
* ```
*/
async linkCheck(id = "latest", follow = false) {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/link-check`,
{ params: { follow } }
)
);
}
/**
* Performs a SpamAssassin check (if enabled) on a specific message.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns The SpamAssassin summary (if enabled)
* @example
* ```typescript
* const spamAssassinCheck = await mailpit.spamAssassinCheck();
* ```
*/
async spamAssassinCheck(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(
`/api/v1/message/${id}/sa-check`
)
);
}
/**
* Retrieves a list of all the unique tags.
* @returns All unique message tags
* @example
* ```typescript
* const tags = await mailpit.getTags();
* ```
*/
async getTags() {
return await this.handleRequest(
() => this.axiosInstance.get(`/api/v1/tags`)
);
}
/**
* Sets and removes tag(s) on message(s). This will overwrite any existing tags for selected message database IDs.
* @param request - The request containing the message IDs and tags. To remove all tags from a message, pass an empty `Tags` array or exclude `Tags` entirely.
* @remarks
* Tags are limited to the following characters: `a-z`, `A-Z`, `0-9`, `-`, `.`, `spaces`, and `_`, and must be a minimum of 1 character.
* Other characters are silently stripped from the tag.
* @returns Plain text "ok" response
* @example
* ```typescript
* // Set tags on message(s)
* await mailpit.setTags({ IDs: ["1", "2", "3"], Tags: ["tag1", "tag2"] });
* // Remove tags from message(s)
* await mailpit.setTags({ IDs: ["1", "2", "3"]});
* ```
*/
async setTags(request) {
return await this.handleRequest(
() => this.axiosInstance.put(`/api/v1/tags`, request)
);
}
/**
* Renames an existing tag.
* @param tag - The current name of the tag.
* @param newTagName - A new name for the tag.
* @remarks
* Tags are limited to the following characters: `a-z`, `A-Z`, `0-9`, `-`, `.`, `spaces`, and `_`, and must be a minimum of 1 character.
* Other characters are silently stripped from the tag.
* @returns Plain text "ok" response
* @example
* ```typescript
* await mailpit.renameTag("Old Tag Name", "New Tag Name");
* ```
*/
async renameTag(tag, newTagName) {
const encodedTag = encodeURIComponent(tag);
return await this.handleRequest(
() => this.axiosInstance.put(`/api/v1/tags/${encodedTag}`, {
Name: newTagName
})
);
}
/**
* Deletes a tag from all messages.
* @param tag - The name of the tag to delete.
* @remarks This does NOT delete any messages
* @returns Plain text "ok" response
* ```typescript
* await mailpit.deleteTag("Tag 1");
* ```
*/
async deleteTag(tag) {
const encodedTag = encodeURIComponent(tag);
return await this.handleRequest(
() => this.axiosInstance.delete(`/api/v1/tags/${encodedTag}`)
);
}
/**
* Retrieves the current Chaos triggers configuration (if enabled).
* @remarks This will return an error if Chaos is not enabled at runtime.
* @returns The Chaos triggers configuration
* @example
* ```typescript
* const triggers = await mailpit.getChaosTriggers();
* ```
*/
async getChaosTriggers() {
return await this.handleRequest(
() => this.axiosInstance.get("/api/v1/chaos")
);
}
/**
* Sets and/or resets the Chaos triggers configuration (if enabled).
* @param triggers - The request containing the chaos triggers. Omitted triggers will reset to the default `0%` probabibility.
* @remarks This will return an error if Chaos is not enabled at runtime.
* @returns The updated Chaos triggers configuration
* @example
* ```typescript
* // Reset all triggers to `0%` probability
* const triggers = await mailpit.setChaosTriggers();
* // Set `Sender` and reset `Authentication` and `Recipient` triggers
* const triggers = await mailpit.setChaosTriggers({ Sender: { ErrorCode: 451, Probability: 5 } });
* ```
*/
async setChaosTriggers(triggers = {}) {
return await this.handleRequest(
() => this.axiosInstance.put(
"/api/v1/chaos",
triggers
)
);
}
/**
* Renders the HTML part of a specific message which can be used for UI integration testing.
* @remarks
* Attached inline images are modified to link to the API provided they exist.
* If the message does not contain an HTML part then a 404 error is returned.
*
*
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @param embed - Whether this route is to be embedded in an iframe. Defaults to `undefined`. Set to `1` to embed.
* The `embed` parameter will add `target="_blank"` and `rel="noreferrer noopener"` to all links.
* In addition, a small script will be added to the end of the document to post (postMessage()) the height of the document back to the parent window for optional iframe height resizing.
* Note that this will also transform the message into a full HTML document (if it isn't already), so this option is useful for viewing but not programmatic testing.
* @returns Rendered HTML
* @example
* ```typescript
* const html = await mailpit.renderMessageHTML();
* ```
*/
async renderMessageHTML(id = "latest", embed) {
return await this.handleRequest(
() => this.axiosInstance.get(`/view/${id}.html`, { params: { embed } })
);
}
/**
* Renders just the message's text part which can be used for UI integration testing.
* @param id - The message database ID. Defaults to `latest` to return the latest message.
* @returns Plain text
* @example
* ```typescript
* const html = await mailpit.renderMessageText();
* ```
*/
async renderMessageText(id = "latest") {
return await this.handleRequest(
() => this.axiosInstance.get(`/view/${id}.txt`)
);
}
};
export {
MailpitClient
};