mirror of
https://github.com/axllent/mailpit.git
synced 2025-03-07 15:21:01 +02:00
Merge branch 'release/v1.5.0'
This commit is contained in:
commit
c4a695e627
19
CHANGELOG.md
19
CHANGELOG.md
@ -2,6 +2,22 @@
|
|||||||
|
|
||||||
Notable changes to Mailpit will be documented in this file.
|
Notable changes to Mailpit will be documented in this file.
|
||||||
|
|
||||||
|
## [v1.5.0]
|
||||||
|
|
||||||
|
### API
|
||||||
|
- Return received datetime when message does not contain a date header
|
||||||
|
|
||||||
|
### Bugfix
|
||||||
|
- Fix JavaScript error when adding the first tag manually
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
- OpenAPI / Swagger schema
|
||||||
|
- Download raw message, HTML/text body parts or attachments via single button
|
||||||
|
- Rename SSL to TLS, add deprecation warnings to flags & ENV variables referring to SSL
|
||||||
|
- Options to support auth without STARTTLS, and accept any login
|
||||||
|
- Option to use message dates as received dates (new messages only)
|
||||||
|
|
||||||
|
|
||||||
## [v1.4.0]
|
## [v1.4.0]
|
||||||
|
|
||||||
### API
|
### API
|
||||||
@ -427,6 +443,3 @@ This release includes a major backend storage change (SQLite) that will render a
|
|||||||
|
|
||||||
### Feature
|
### Feature
|
||||||
- Unread statistics
|
- Unread statistics
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||

|

|
||||||
[](https://goreportcard.com/report/github.com/axllent/mailpit)
|
[](https://goreportcard.com/report/github.com/axllent/mailpit)
|
||||||
|
|
||||||
Mailpit is a multi-platform email testing tool 7 API for developers.
|
Mailpit is a multi-platform email testing tool & API for developers.
|
||||||
|
|
||||||
It acts as both an SMTP server, and provides a web interface to view all captured emails.
|
It acts as both an SMTP server, and provides a web interface to view all captured emails.
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ Mailpit provides a simple REST API to access and delete stored messages.
|
|||||||
|
|
||||||
If the Mailpit server is set to use Basic Authentication, then API requests must use Basic Authentication too.
|
If the Mailpit server is set to use Basic Authentication, then API requests must use Basic Authentication too.
|
||||||
|
|
||||||
|
You can view the Swagger API documentation directly within Mailpit by going to `http://0.0.0.0:8025/api/v1/`.
|
||||||
|
|
||||||
The API is split into three main parts:
|
The API is split into three main parts:
|
||||||
|
|
||||||
- [Messages](Messages.md) - Listing, deleting & marking messages as read/unread.
|
- [Messages](Messages.md) - Listing, deleting & marking messages as read/unread.
|
||||||
|
@ -5,20 +5,25 @@ import { sassPlugin } from 'esbuild-sass-plugin'
|
|||||||
const doWatch = process.env.WATCH == 'true' ? true : false;
|
const doWatch = process.env.WATCH == 'true' ? true : false;
|
||||||
const doMinify = process.env.MINIFY == 'true' ? true : false;
|
const doMinify = process.env.MINIFY == 'true' ? true : false;
|
||||||
|
|
||||||
const ctx = await esbuild.context({
|
const ctx = await esbuild.context(
|
||||||
entryPoints: ["server/ui-src/app.js"],
|
{
|
||||||
bundle: true,
|
entryPoints: [
|
||||||
minify: doMinify,
|
"server/ui-src/app.js",
|
||||||
sourcemap: false,
|
"server/ui-src/docs.js"
|
||||||
outfile: "server/ui/dist/app.js",
|
],
|
||||||
plugins: [pluginVue(), sassPlugin()],
|
bundle: true,
|
||||||
loader: {
|
minify: doMinify,
|
||||||
".svg": "file",
|
sourcemap: false,
|
||||||
".woff": "file",
|
outdir: "server/ui/dist/",
|
||||||
".woff2": "file",
|
plugins: [pluginVue(), sassPlugin()],
|
||||||
},
|
loader: {
|
||||||
logLevel: "info"
|
".svg": "file",
|
||||||
})
|
".woff": "file",
|
||||||
|
".woff2": "file",
|
||||||
|
},
|
||||||
|
logLevel: "info"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (doWatch) {
|
if (doWatch) {
|
||||||
await ctx.watch()
|
await ctx.watch()
|
||||||
|
2717
package-lock.json
generated
2717
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@
|
|||||||
"bootstrap5-tags": "^1.4.41",
|
"bootstrap5-tags": "^1.4.41",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
|
"rapidoc": "^9.3.4",
|
||||||
"tinycon": "^0.6.8",
|
"tinycon": "^0.6.8",
|
||||||
"vue": "^3.2.13"
|
"vue": "^3.2.13"
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,34 @@ import (
|
|||||||
|
|
||||||
// GetMessages returns a paginated list of messages as JSON
|
// GetMessages returns a paginated list of messages as JSON
|
||||||
func GetMessages(w http.ResponseWriter, r *http.Request) {
|
func GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/messages messages GetMessages
|
||||||
|
//
|
||||||
|
// # List messages
|
||||||
|
//
|
||||||
|
// Returns messages from the mailbox ordered from newest to oldest.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: start
|
||||||
|
// in: query
|
||||||
|
// description: pagination offset
|
||||||
|
// required: false
|
||||||
|
// type: integer
|
||||||
|
// default: 0
|
||||||
|
// + name: limit
|
||||||
|
// in: query
|
||||||
|
// description: limit results
|
||||||
|
// required: false
|
||||||
|
// type: integer
|
||||||
|
// default: 50
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: MessagesSummaryResponse
|
||||||
|
// default: ErrorResponse
|
||||||
start, limit := getStartLimit(r)
|
start, limit := getStartLimit(r)
|
||||||
|
|
||||||
messages, err := storage.List(start, limit)
|
messages, err := storage.List(start, limit)
|
||||||
@ -40,11 +68,38 @@ func GetMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = w.Write(bytes)
|
_, _ = w.Write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search returns up to 200 of the latest messages as JSON
|
// Search returns the latest messages as JSON
|
||||||
func Search(w http.ResponseWriter, r *http.Request) {
|
func Search(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/search messages MessagesSummary
|
||||||
|
//
|
||||||
|
// # Search messages
|
||||||
|
//
|
||||||
|
// Returns the latest messages matching a search.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: query
|
||||||
|
// in: query
|
||||||
|
// description: search query
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
// + name: limit
|
||||||
|
// in: query
|
||||||
|
// description: limit results
|
||||||
|
// required: false
|
||||||
|
// type: integer
|
||||||
|
// default: 50
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: MessagesSummaryResponse
|
||||||
|
// default: ErrorResponse
|
||||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||||
if search == "" {
|
if search == "" {
|
||||||
fourOFour(w)
|
httpError(w, "Error: no search query")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,15 +127,37 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = w.Write(bytes)
|
_, _ = w.Write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage (method: GET) returns the *data.Message as JSON
|
// GetMessage (method: GET) returns the Message as JSON
|
||||||
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/message/{ID} message Message
|
||||||
|
//
|
||||||
|
// # Get message summary
|
||||||
|
//
|
||||||
|
// Returns the summary of a message, marking the message as read.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ID
|
||||||
|
// in: path
|
||||||
|
// description: message id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: Message
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
msg, err := storage.GetMessage(id)
|
msg, err := storage.GetMessage(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, "Message not found")
|
fourOFour(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +168,35 @@ func GetMessage(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DownloadAttachment (method: GET) returns the attachment data
|
// DownloadAttachment (method: GET) returns the attachment data
|
||||||
func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/message/{ID}/part/{PartID} message Attachment
|
||||||
|
//
|
||||||
|
// # Get message attachment
|
||||||
|
//
|
||||||
|
// This will return the attachment part using the appropriate Content-Type.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/*
|
||||||
|
// - image/*
|
||||||
|
// - text/*
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ID
|
||||||
|
// in: path
|
||||||
|
// description: message id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
// + name: PartID
|
||||||
|
// in: path
|
||||||
|
// description: attachment part id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: BinaryResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
@ -98,7 +204,7 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
a, err := storage.GetAttachmentPart(id, partID)
|
a, err := storage.GetAttachmentPart(id, partID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, err.Error())
|
fourOFour(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileName := a.FileName
|
fileName := a.FileName
|
||||||
@ -111,15 +217,37 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = w.Write(a.Content)
|
_, _ = w.Write(a.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers (method: GET) returns the message headers as JSON
|
// GetHeaders (method: GET) returns the message headers as JSON
|
||||||
func Headers(w http.ResponseWriter, r *http.Request) {
|
func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/message/{ID}/headers message Headers
|
||||||
|
//
|
||||||
|
// # Get message headers
|
||||||
|
//
|
||||||
|
// Returns the message headers as an array.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ID
|
||||||
|
// in: path
|
||||||
|
// description: message id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: MessageHeaders
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
data, err := storage.GetMessageRaw(id)
|
data, err := storage.GetMessageRaw(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, err.Error())
|
fourOFour(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,8 +258,7 @@ func Headers(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := m.Header
|
bytes, _ := json.Marshal(m.Header)
|
||||||
bytes, _ := json.Marshal(headers)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
_, _ = w.Write(bytes)
|
_, _ = w.Write(bytes)
|
||||||
@ -139,6 +266,28 @@ func Headers(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DownloadRaw (method: GET) returns the full email source as plain text
|
// DownloadRaw (method: GET) returns the full email source as plain text
|
||||||
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/message/{ID}/raw message Raw
|
||||||
|
//
|
||||||
|
// # Get message source
|
||||||
|
//
|
||||||
|
// Returns the full email source as plain text.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - text/plain
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ID
|
||||||
|
// in: path
|
||||||
|
// description: message id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: TextResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
@ -147,7 +296,7 @@ func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
data, err := storage.GetMessageRaw(id)
|
data, err := storage.GetMessageRaw(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, err.Error())
|
fourOFour(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +308,32 @@ func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMessages (method: DELETE) deletes all messages matching IDS.
|
// DeleteMessages (method: DELETE) deletes all messages matching IDS.
|
||||||
// If no IDs are provided then all messages are deleted.
|
|
||||||
func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route DELETE /api/v1/messages messages Delete
|
||||||
|
//
|
||||||
|
// # Delete messages
|
||||||
|
//
|
||||||
|
// If no IDs are provided then all messages are deleted.
|
||||||
|
//
|
||||||
|
// Consumes:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - text/plain
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ids
|
||||||
|
// in: body
|
||||||
|
// description: Message ids to delete
|
||||||
|
// required: false
|
||||||
|
// type: DeleteRequest
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: OKResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
var data struct {
|
var data struct {
|
||||||
IDs []string
|
IDs []string
|
||||||
@ -185,7 +358,33 @@ func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs
|
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs
|
||||||
|
// If no IDs are provided then all messages are updated.
|
||||||
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route PUT /api/v1/messages messages SetReadStatus
|
||||||
|
//
|
||||||
|
// # Set read status
|
||||||
|
//
|
||||||
|
// If no IDs are provided then all messages are updated.
|
||||||
|
//
|
||||||
|
// Consumes:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - text/plain
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ids
|
||||||
|
// in: body
|
||||||
|
// description: Message ids to update
|
||||||
|
// required: false
|
||||||
|
// type: SetReadStatusRequest
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: OKResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
@ -239,6 +438,31 @@ func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// SetTags (method: PUT) will set the tags for all provided IDs
|
// SetTags (method: PUT) will set the tags for all provided IDs
|
||||||
func SetTags(w http.ResponseWriter, r *http.Request) {
|
func SetTags(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route PUT /api/v1/tags tags SetTags
|
||||||
|
//
|
||||||
|
// # Set message tags
|
||||||
|
//
|
||||||
|
// To remove all tags from a message, pass an empty tags array.
|
||||||
|
//
|
||||||
|
// Consumes:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - text/plain
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ids
|
||||||
|
// in: body
|
||||||
|
// description: Message ids to update
|
||||||
|
// required: true
|
||||||
|
// type: SetTagsRequest
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: OKResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
|
@ -11,19 +11,41 @@ import (
|
|||||||
"github.com/axllent/mailpit/utils/updater"
|
"github.com/axllent/mailpit/utils/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type appVersion struct {
|
// Response includes the current and latest Mailpit versions, database info, and memory usage
|
||||||
Version string
|
//
|
||||||
|
// swagger:model AppInformation
|
||||||
|
type appInformation struct {
|
||||||
|
// Current Mailpit version
|
||||||
|
Version string
|
||||||
|
// Latest Mailpit version
|
||||||
LatestVersion string
|
LatestVersion string
|
||||||
Database string
|
// Database path
|
||||||
DatabaseSize int64
|
Database string
|
||||||
Messages int
|
// Database size in bytes
|
||||||
Memory uint64
|
DatabaseSize int64
|
||||||
|
// Total number of messages in the database
|
||||||
|
Messages int
|
||||||
|
// Current memory usage in bytes
|
||||||
|
Memory uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppInfo returns some basic details about the running app, and latest release.
|
// AppInfo returns some basic details about the running app, and latest release.
|
||||||
func AppInfo(w http.ResponseWriter, r *http.Request) {
|
func AppInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/info application AppInformation
|
||||||
info := appVersion{}
|
//
|
||||||
|
// # Get the application information
|
||||||
|
//
|
||||||
|
// Returns basic runtime information, message totals and latest release version.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - application/octet-stream
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: InfoResponse
|
||||||
|
// default: ErrorResponse
|
||||||
|
info := appInformation{}
|
||||||
info.Version = config.Version
|
info.Version = config.Version
|
||||||
|
|
||||||
var m runtime.MemStats
|
var m runtime.MemStats
|
||||||
|
@ -1,6 +1,30 @@
|
|||||||
package apiv1
|
package apiv1
|
||||||
|
|
||||||
import "github.com/axllent/mailpit/storage"
|
import (
|
||||||
|
"github.com/axllent/mailpit/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessagesSummary is a summary of a list of messages
|
||||||
|
type MessagesSummary struct {
|
||||||
|
// Total number of messages in mailbox
|
||||||
|
Total int `json:"total"`
|
||||||
|
|
||||||
|
// Total number of unread messages in mailbox
|
||||||
|
Unread int `json:"unread"`
|
||||||
|
|
||||||
|
// Number of results returned
|
||||||
|
Count int `json:"count"`
|
||||||
|
|
||||||
|
// Pagination offset
|
||||||
|
Start int `json:"start"`
|
||||||
|
|
||||||
|
// All current tags
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
|
||||||
|
// Messages summary
|
||||||
|
// in:body
|
||||||
|
Messages []storage.MessageSummary `json:"messages"`
|
||||||
|
}
|
||||||
|
|
||||||
// The following structs & aliases are provided for easy import
|
// The following structs & aliases are provided for easy import
|
||||||
// and understanding of the JSON structure.
|
// and understanding of the JSON structure.
|
||||||
@ -8,16 +32,6 @@ import "github.com/axllent/mailpit/storage"
|
|||||||
// MessageSummary - summary of a single message
|
// MessageSummary - summary of a single message
|
||||||
type MessageSummary = storage.MessageSummary
|
type MessageSummary = storage.MessageSummary
|
||||||
|
|
||||||
// MessagesSummary - summary of a list of messages
|
|
||||||
type MessagesSummary struct {
|
|
||||||
Total int `json:"total"`
|
|
||||||
Unread int `json:"unread"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
Start int `json:"start"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Messages []MessageSummary `json:"messages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message data
|
// Message data
|
||||||
type Message = storage.Message
|
type Message = storage.Message
|
||||||
|
|
||||||
|
19
server/apiv1/swagger-config.yml
Normal file
19
server/apiv1/swagger-config.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
info:
|
||||||
|
description: |-
|
||||||
|
OpenAPI 2.0 documentation for [Mailpit](https://github.com/axllent/mailpit).
|
||||||
|
title: Mailpit API
|
||||||
|
contact:
|
||||||
|
name: GitHub
|
||||||
|
url: https://github.com/axllent/mailpit
|
||||||
|
license:
|
||||||
|
name: MIT license
|
||||||
|
url: https://github.com/axllent/mailpit/blob/develop/LICENSE
|
||||||
|
version: "v1"
|
||||||
|
paths: {}
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
schemes:
|
||||||
|
- http
|
||||||
|
swagger: "2.0"
|
83
server/apiv1/swagger.go
Normal file
83
server/apiv1/swagger.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package apiv1
|
||||||
|
|
||||||
|
// These structs are for the purpose of defining swagger HTTP responses
|
||||||
|
|
||||||
|
// Application information
|
||||||
|
// swagger:response InfoResponse
|
||||||
|
type infoResponse struct {
|
||||||
|
// Application information
|
||||||
|
Body appInformation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message summary
|
||||||
|
// swagger:response MessagesSummaryResponse
|
||||||
|
type messagesSummaryResponse struct {
|
||||||
|
// The message summary
|
||||||
|
// in: body
|
||||||
|
Body MessagesSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message headers
|
||||||
|
// swagger:model MessageHeaders
|
||||||
|
type messageHeaders map[string][]string
|
||||||
|
|
||||||
|
// Delete request
|
||||||
|
// swagger:model DeleteRequest
|
||||||
|
type deleteRequest struct {
|
||||||
|
// ids
|
||||||
|
// in:body
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set read status request
|
||||||
|
// swagger:model SetReadStatusRequest
|
||||||
|
type setReadStatusRequest struct {
|
||||||
|
// Read status
|
||||||
|
Read bool `json:"read"`
|
||||||
|
|
||||||
|
// ids
|
||||||
|
// in:body
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tags request
|
||||||
|
// swagger:model SetTagsRequest
|
||||||
|
type setTagsRequest struct {
|
||||||
|
// Tags
|
||||||
|
// in:body
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
|
||||||
|
// ids
|
||||||
|
// in:body
|
||||||
|
IDs []string `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary data reponse inherits the attachment's content type
|
||||||
|
// swagger:response BinaryResponse
|
||||||
|
type binaryResponse struct {
|
||||||
|
// in: body
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain text response
|
||||||
|
// swagger:response TextResponse
|
||||||
|
type textResponse struct {
|
||||||
|
// in: body
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error reponse
|
||||||
|
// swagger:response ErrorResponse
|
||||||
|
type errorResponse struct {
|
||||||
|
// The error message
|
||||||
|
// in: body
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain text "ok" reponse
|
||||||
|
// swagger:response OKResponse
|
||||||
|
type okResponse struct {
|
||||||
|
// Default reponse
|
||||||
|
// in: body
|
||||||
|
Body string
|
||||||
|
}
|
@ -24,6 +24,33 @@ var (
|
|||||||
|
|
||||||
// Thumbnail returns a thumbnail image for an attachment (images only)
|
// Thumbnail returns a thumbnail image for an attachment (images only)
|
||||||
func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// swagger:route GET /api/v1/message/{ID}/part/{PartID}/thumb message Thumbnail
|
||||||
|
//
|
||||||
|
// # Get an attachment image thumbnail
|
||||||
|
//
|
||||||
|
// This will return a cropped 180x120 JPEG thumbnail of an image attachment.
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Produces:
|
||||||
|
// - image/jpeg
|
||||||
|
//
|
||||||
|
// Schemes: http, https
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// + name: ID
|
||||||
|
// in: path
|
||||||
|
// description: message id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
// + name: PartID
|
||||||
|
// in: path
|
||||||
|
// description: attachment part id
|
||||||
|
// required: true
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: BinaryResponse
|
||||||
|
// default: ErrorResponse
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
@ -85,7 +85,7 @@ func defaultRoutes() *mux.Router {
|
|||||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/raw", middleWareFunc(apiv1.DownloadRaw)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/message/{id}/raw", middleWareFunc(apiv1.DownloadRaw)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}/headers", middleWareFunc(apiv1.Headers)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/message/{id}/headers", middleWareFunc(apiv1.GetHeaders)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/message/{id}", middleWareFunc(apiv1.GetMessage)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/message/{id}", middleWareFunc(apiv1.GetMessage)).Methods("GET")
|
||||||
r.HandleFunc(config.Webroot+"api/v1/info", middleWareFunc(apiv1.AppInfo)).Methods("GET")
|
r.HandleFunc(config.Webroot+"api/v1/info", middleWareFunc(apiv1.AppInfo)).Methods("GET")
|
||||||
|
|
||||||
|
@ -128,9 +128,8 @@ export default {
|
|||||||
self.start = response.data.start;
|
self.start = response.data.start;
|
||||||
self.items = response.data.messages;
|
self.items = response.data.messages;
|
||||||
self.tags = response.data.tags;
|
self.tags = response.data.tags;
|
||||||
if (!self.existingTags.length) {
|
self.existingTags = JSON.parse(JSON.stringify(self.tags));
|
||||||
self.existingTags = JSON.parse(JSON.stringify(self.tags));
|
|
||||||
}
|
|
||||||
// if pagination > 0 && results == 0 reload first page (prune)
|
// if pagination > 0 && results == 0 reload first page (prune)
|
||||||
if (response.data.count == 0 && response.data.start > 0) {
|
if (response.data.count == 0 && response.data.start > 0) {
|
||||||
self.start = 0;
|
self.start = 0;
|
||||||
@ -543,6 +542,14 @@ export default {
|
|||||||
self.appInfo = response.data;
|
self.appInfo = response.data;
|
||||||
self.modal('AppInfoModal').show();
|
self.modal('AppInfoModal').show();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
downloadMessageBody: function (str, ext) {
|
||||||
|
let dl = document.createElement('a');
|
||||||
|
dl.href = "data:text/plain," + encodeURIComponent(str);
|
||||||
|
dl.target = '_blank';
|
||||||
|
dl.download = this.message.ID + '.' + ext;
|
||||||
|
dl.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -576,10 +583,49 @@ export default {
|
|||||||
:href="'#' + messagePrev" title="View previous message">
|
:href="'#' + messagePrev" title="View previous message">
|
||||||
<i class="bi bi-caret-left-fill"></i>
|
<i class="bi bi-caret-left-fill"></i>
|
||||||
</a>
|
</a>
|
||||||
<a :href="'api/v1/message/' + message.ID + '/raw?dl=1'" class="btn btn-outline-light me-2 float-end"
|
<div class="dropdown float-end" id="DownloadBtn">
|
||||||
title="Download message">
|
<button type="button" class="btn btn-outline-light dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
<i class="bi bi-file-arrow-down-fill"></i> <span class="d-none d-md-inline">Download</span>
|
aria-expanded="false">
|
||||||
</a>
|
<i class="bi bi-file-arrow-down-fill"></i>
|
||||||
|
<span class="d-none d-md-inline ms-1">Download</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a :href="'api/v1/message/' + message.ID + '/raw?dl=1'" class="dropdown-item"
|
||||||
|
title="Message source including headers, body and attachments">
|
||||||
|
Raw message
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li v-if="message.HTML">
|
||||||
|
<button v-on:click="downloadMessageBody(message.HTML, 'html')" class="dropdown-item">
|
||||||
|
HTML body
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="message.Text">
|
||||||
|
<button v-on:click="downloadMessageBody(message.Text, 'txt')" class="dropdown-item">
|
||||||
|
Text body
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="allAttachments(message).length">
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li v-for="part in allAttachments(message)">
|
||||||
|
<a :href="'api/v1/message/' + message.ID + '/part/' + part.PartID" type="button"
|
||||||
|
class="row m-0 dropdown-item d-flex" target="_blank"
|
||||||
|
:title="part.FileName != '' ? part.FileName : '[ unknown ]'" style="min-width: 350px">
|
||||||
|
<div class="col-auto p-0 pe-1">
|
||||||
|
<i class="bi" :class="attachmentIcon(part)"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col text-truncate p-0 pe-1">
|
||||||
|
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto text-muted small p-0">
|
||||||
|
{{ getFileSize(part.Size) }}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col col-md-9 col-lg-5" v-if="!message">
|
<div class="col col-md-9 col-lg-5" v-if="!message">
|
||||||
@ -590,13 +636,13 @@ export default {
|
|||||||
<span v-if="!total" class="ms-2">Mailpit</span>
|
<span v-if="!total" class="ms-2">Mailpit</span>
|
||||||
</a>
|
</a>
|
||||||
<div v-if="total" class="ms-md-2 d-flex bg-white border rounded-start flex-fill position-relative">
|
<div v-if="total" class="ms-md-2 d-flex bg-white border rounded-start flex-fill position-relative">
|
||||||
<input type="text" class="form-control border-0" v-model.trim="search"
|
<input type="text" class="form-control border-0" v-model.trim="search" placeholder="Search mailbox">
|
||||||
placeholder="Search mailbox">
|
|
||||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
||||||
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<button v-if="total" class="btn btn-outline-light" type="submit"><i
|
<button v-if="total" class="btn btn-outline-light" type="submit">
|
||||||
class="bi bi-search"></i></button>
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -618,6 +664,7 @@ export default {
|
|||||||
<option value="100">100</option>
|
<option value="100">100</option>
|
||||||
<option value="200">200</option>
|
<option value="200">200</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<span v-if="searching">
|
<span v-if="searching">
|
||||||
<b>{{ formatNumber(items.length) }} result<template v-if="items.length != 1">s</template></b>
|
<b>{{ formatNumber(items.length) }} result<template v-if="items.length != 1">s</template></b>
|
||||||
</span>
|
</span>
|
||||||
@ -626,8 +673,8 @@ export default {
|
|||||||
{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }} <small>of</small>
|
{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }} <small>of</small>
|
||||||
{{ formatNumber(total) }}
|
{{ formatNumber(total) }}
|
||||||
</small>
|
</small>
|
||||||
<button class="btn btn-outline-light ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev"
|
<button class="btn btn-outline-light ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev" v-if="!searching"
|
||||||
v-if="!searching" :title="'View previous ' + limit + ' messages'">
|
:title="'View previous ' + limit + ' messages'">
|
||||||
<i class="bi bi-caret-left-fill"></i>
|
<i class="bi bi-caret-left-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-light" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
<button class="btn btn-outline-light" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
||||||
@ -679,16 +726,16 @@ export default {
|
|||||||
<button class="list-group-item list-group-item-action" :disabled="!selectedHasUnread()"
|
<button class="list-group-item list-group-item-action" :disabled="!selectedHasUnread()"
|
||||||
v-on:click="markSelectedRead">
|
v-on:click="markSelectedRead">
|
||||||
<i class="bi bi-eye-fill"></i>
|
<i class="bi bi-eye-fill"></i>
|
||||||
Mark selected read
|
Mark read
|
||||||
</button>
|
</button>
|
||||||
<button class="list-group-item list-group-item-action" :disabled="!selectedHasRead()"
|
<button class="list-group-item list-group-item-action" :disabled="!selectedHasRead()"
|
||||||
v-on:click="markSelectedUnread">
|
v-on:click="markSelectedUnread">
|
||||||
<i class="bi bi-eye-slash"></i>
|
<i class="bi bi-eye-slash"></i>
|
||||||
Mark selected unread
|
Mark unread
|
||||||
</button>
|
</button>
|
||||||
<button class="list-group-item list-group-item-action" v-on:click="deleteMessages">
|
<button class="list-group-item list-group-item-action" v-on:click="deleteMessages">
|
||||||
<i class="bi bi-trash-fill me-1 text-danger"></i>
|
<i class="bi bi-trash-fill me-1 text-danger"></i>
|
||||||
Delete selected
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<button class="list-group-item list-group-item-action" v-on:click="selected = []">
|
<button class="list-group-item list-group-item-action" v-on:click="selected = []">
|
||||||
<i class="bi bi-x-circle me-1"></i>
|
<i class="bi bi-x-circle me-1"></i>
|
||||||
@ -735,13 +782,13 @@ export default {
|
|||||||
<div class="text-truncate d-lg-none privacy">
|
<div class="text-truncate d-lg-none privacy">
|
||||||
<span v-if="message.From" :title="message.From.Address">{{
|
<span v-if="message.From" :title="message.From.Address">{{
|
||||||
message.From.Name ?
|
message.From.Name ?
|
||||||
message.From.Name : message.From.Address
|
message.From.Name : message.From.Address
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-truncate d-none d-lg-block privacy">
|
<div class="text-truncate d-none d-lg-block privacy">
|
||||||
<b v-if="message.From" :title="message.From.Address">{{
|
<b v-if="message.From" :title="message.From.Address">{{
|
||||||
message.From.Name ?
|
message.From.Name ?
|
||||||
message.From.Name : message.From.Address
|
message.From.Name : message.From.Address
|
||||||
}}</b>
|
}}</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
||||||
@ -810,8 +857,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel"
|
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -874,6 +920,12 @@ export default {
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<a class="btn btn-primary w-100" href="api/v1/" target="_blank">
|
||||||
|
<i class="bi bi-braces"></i>
|
||||||
|
OpenAPI / Swagger API documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit" target="_blank">
|
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit" target="_blank">
|
||||||
<i class="bi bi-github"></i>
|
<i class="bi bi-github"></i>
|
||||||
@ -882,8 +934,7 @@ export default {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki"
|
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki" target="_blank">
|
||||||
target="_blank">
|
|
||||||
Documentation
|
Documentation
|
||||||
<i class="bi bi-box-arrow-up-right"></i>
|
<i class="bi bi-box-arrow-up-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -149,10 +149,6 @@ body.blur {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .tag.active {
|
|
||||||
// font-weight: bold;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.form-select.tag-selector {
|
.form-select.tag-selector {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -166,6 +162,17 @@ body.blur {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#DownloadBtn {
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
position: static;
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* PrismJS 1.29.0 - modified!
|
/* PrismJS 1.29.0 - modified!
|
||||||
https://prismjs.com/download.html#themes=prism-coy&languages=markup+css */
|
https://prismjs.com/download.html#themes=prism-coy&languages=markup+css */
|
||||||
code[class*="language-"],
|
code[class*="language-"],
|
||||||
|
1
server/ui-src/docs.js
Normal file
1
server/ui-src/docs.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import "rapidoc";
|
@ -2,8 +2,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import commonMixins from '../mixins.js';
|
import commonMixins from '../mixins.js';
|
||||||
import Prism from "prismjs";
|
import Prism from "prismjs";
|
||||||
|
import Tags from "bootstrap5-tags";
|
||||||
import Attachments from './Attachments.vue';
|
import Attachments from './Attachments.vue';
|
||||||
import MessageTags from './MessageTags.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@ -12,8 +12,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Attachments,
|
Attachments
|
||||||
MessageTags
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [commonMixins],
|
mixins: [commonMixins],
|
||||||
@ -22,36 +21,54 @@ export default {
|
|||||||
return {
|
return {
|
||||||
srcURI: false,
|
srcURI: false,
|
||||||
iframes: [], // for resizing
|
iframes: [], // for resizing
|
||||||
tagComponent: false, // to force rerendering of component
|
showTags: false, // to force rerendering of component
|
||||||
|
messageTags: [],
|
||||||
|
allTags: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
message: {
|
message: {
|
||||||
handler(newQuestion) {
|
handler() {
|
||||||
let self = this;
|
let self = this;
|
||||||
self.tagComponent = false;
|
self.showTags = false;
|
||||||
|
self.messageTags = self.message.Tags;
|
||||||
|
self.allTags = self.existingTags;
|
||||||
// delay to select first tab and add HTML highlighting (prev/next)
|
// delay to select first tab and add HTML highlighting (prev/next)
|
||||||
self.$nextTick(function () {
|
self.$nextTick(function () {
|
||||||
self.renderUI();
|
self.renderUI();
|
||||||
self.tagComponent = true;
|
self.showTags = true;
|
||||||
|
self.$nextTick(function () {
|
||||||
|
Tags.init("select[multiple]");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// force eager callback execution
|
// force eager callback execution
|
||||||
immediate: true
|
immediate: true
|
||||||
|
},
|
||||||
|
messageTags() {
|
||||||
|
// save changed to tags
|
||||||
|
if (this.showTags) {
|
||||||
|
this.saveTags();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
let self = this;
|
let self = this;
|
||||||
self.tagComponent = false;
|
self.showTags = false;
|
||||||
|
self.allTags = self.existingTags;
|
||||||
window.addEventListener("resize", self.resizeIframes);
|
window.addEventListener("resize", self.resizeIframes);
|
||||||
self.renderUI();
|
self.renderUI();
|
||||||
var tabEl = document.getElementById('nav-raw-tab');
|
var tabEl = document.getElementById('nav-raw-tab');
|
||||||
tabEl.addEventListener('shown.bs.tab', function (event) {
|
tabEl.addEventListener('shown.bs.tab', function (event) {
|
||||||
self.srcURI = 'api/v1/message/' + self.message.ID + '/raw';
|
self.srcURI = 'api/v1/message/' + self.message.ID + '/raw';
|
||||||
});
|
});
|
||||||
self.tagComponent = true;
|
|
||||||
|
self.showTags = true;
|
||||||
|
self.$nextTick(function () {
|
||||||
|
Tags.init("select[multiple]");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
unmounted: function () {
|
unmounted: function () {
|
||||||
@ -105,6 +122,20 @@ export default {
|
|||||||
if (s) {
|
if (s) {
|
||||||
s.style.height = s.contentWindow.document.body.scrollHeight + 50 + 'px';
|
s.style.height = s.contentWindow.document.body.scrollHeight + 50 + 'px';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveTags: function () {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
ids: [this.message.ID],
|
||||||
|
tags: this.messageTags
|
||||||
|
}
|
||||||
|
|
||||||
|
self.put('api/v1/tags', data, function (response) {
|
||||||
|
self.scrollInPlace = true;
|
||||||
|
self.$emit('loadMessages');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,31 +193,30 @@ export default {
|
|||||||
<th class="small">Date</th>
|
<th class="small">Date</th>
|
||||||
<td>{{ messageDate(message.Date) }}</td>
|
<td>{{ messageDate(message.Date) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<MessageTags :message="message" :existingTags="existingTags"
|
|
||||||
@load-messages="$emit('loadMessages')" v-if="tagComponent">
|
<tr class="small" v-if="showTags">
|
||||||
</MessageTags>
|
<th>Tags</th>
|
||||||
|
<td>
|
||||||
|
<select class="form-select small tag-selector" v-model="messageTags" multiple
|
||||||
|
data-allow-new="true" data-clear-end="true" data-allow-clear="true"
|
||||||
|
data-placeholder="Add tags..." data-badge-style="secondary"
|
||||||
|
data-regex="^([a-zA-Z0-9\-\ \_]){3,}$" data-separator="|,|">
|
||||||
|
<option value="">Type a tag...</option>
|
||||||
|
<!-- you need at least one option with the placeholder -->
|
||||||
|
<option v-for="t in allTags" :value="t">{{ t }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="invalid-feedback">Please select a valid tag.</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-auto text-md-end mt-md-3">
|
<div class="col-md-auto d-none d-md-block text-end mt-md-3">
|
||||||
<!-- <p class="text-muted small d-none d-md-block mb-2"><small>{{ messageDate(message.Date) }}</small></p>
|
<div class="mt-2 mt-md-0" v-if="allAttachments(message)">
|
||||||
<p class="text-muted small d-none d-md-block"><small>Size: {{ getFileSize(message.Size) }}</small></p> -->
|
<span class="badge rounded-pill text-bg-secondary p-2">
|
||||||
<div class="dropdown mt-2 mt-md-0" v-if="allAttachments(message)">
|
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false">
|
|
||||||
Attachment<span v-if="allAttachments(message).length > 1">s</span>
|
Attachment<span v-if="allAttachments(message).length > 1">s</span>
|
||||||
({{ allAttachments(message).length }})
|
({{ allAttachments(message).length }})
|
||||||
</button>
|
</span>
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li v-for="part in allAttachments(message)">
|
|
||||||
<a :href="'api/v1/message/' + message.ID + '/part/' + part.PartID" type="button"
|
|
||||||
class="dropdown-item" target="_blank">
|
|
||||||
<i class="bi" :class="attachmentIcon(part)"></i>
|
|
||||||
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
|
||||||
<small class="text-muted ms-2">{{ getFileSize(part.Size) }}</small>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,8 +226,8 @@ export default {
|
|||||||
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab" data-bs-target="#nav-html" type="button"
|
<button class="nav-link" id="nav-html-tab" data-bs-toggle="tab" data-bs-target="#nav-html" type="button"
|
||||||
role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML">HTML</button>
|
role="tab" aria-controls="nav-html" aria-selected="true" v-if="message.HTML">HTML</button>
|
||||||
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab" data-bs-target="#nav-html-source"
|
<button class="nav-link" id="nav-html-source-tab" data-bs-toggle="tab" data-bs-target="#nav-html-source"
|
||||||
type="button" role="tab" aria-controls="nav-html-source" aria-selected="false"
|
type="button" role="tab" aria-controls="nav-html-source" aria-selected="false" v-if="message.HTML">HTML
|
||||||
v-if="message.HTML">HTML Source</button>
|
Source</button>
|
||||||
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab" data-bs-target="#nav-plain-text"
|
<button class="nav-link" id="nav-plain-text-tab" data-bs-toggle="tab" data-bs-target="#nav-plain-text"
|
||||||
type="button" role="tab" aria-controls="nav-plain-text" aria-selected="false"
|
type="button" role="tab" aria-controls="nav-plain-text" aria-selected="false"
|
||||||
:class="message.HTML == '' ? 'show' : ''">Text</button>
|
:class="message.HTML == '' ? 'show' : ''">Text</button>
|
||||||
@ -208,8 +238,8 @@ export default {
|
|||||||
<div class="tab-content mb-5" id="nav-tabContent">
|
<div class="tab-content mb-5" id="nav-tabContent">
|
||||||
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
|
<div v-if="message.HTML != ''" class="tab-pane fade show" id="nav-html" role="tabpanel"
|
||||||
aria-labelledby="nav-html-tab" tabindex="0">
|
aria-labelledby="nav-html-tab" tabindex="0">
|
||||||
<iframe target-blank="" class="tab-pane" id="preview-html" :srcdoc="message.HTML"
|
<iframe target-blank="" class="tab-pane" id="preview-html" :srcdoc="message.HTML" v-on:load="resizeIframe"
|
||||||
v-on:load="resizeIframe" seamless frameborder="0" style="width: 100%; height: 100%;">
|
seamless frameborder="0" style="width: 100%; height: 100%;">
|
||||||
</iframe>
|
</iframe>
|
||||||
<Attachments v-if="allAttachments(message).length" :message="message"
|
<Attachments v-if="allAttachments(message).length" :message="message"
|
||||||
:attachments="allAttachments(message)"></Attachments>
|
:attachments="allAttachments(message)"></Attachments>
|
||||||
@ -218,8 +248,8 @@ export default {
|
|||||||
tabindex="0" v-if="message.HTML">
|
tabindex="0" v-if="message.HTML">
|
||||||
<pre><code class="language-html">{{ message.HTML }}</code></pre>
|
<pre><code class="language-html">{{ message.HTML }}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab"
|
<div class="tab-pane fade" id="nav-plain-text" role="tabpanel" aria-labelledby="nav-plain-text-tab" tabindex="0"
|
||||||
tabindex="0" :class="message.HTML == '' ? 'show' : ''">
|
:class="message.HTML == '' ? 'show' : ''">
|
||||||
<div class="text-view">{{ message.Text }}</div>
|
<div class="text-view">{{ message.Text }}</div>
|
||||||
<Attachments v-if="allAttachments(message).length" :message="message"
|
<Attachments v-if="allAttachments(message).length" :message="message"
|
||||||
:attachments="allAttachments(message)"></Attachments>
|
:attachments="allAttachments(message)"></Attachments>
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
|
|
||||||
<script>
|
|
||||||
import commonMixins from '../mixins.js';
|
|
||||||
import Tags from "bootstrap5-tags";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: Object,
|
|
||||||
existingTags: Array
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [commonMixins],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
messageTags: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
let self = this;
|
|
||||||
self.loaded = false;
|
|
||||||
self.messageTags = self.message.Tags;
|
|
||||||
// delay until vue has rendered
|
|
||||||
self.$nextTick(function () {
|
|
||||||
Tags.init("select[multiple]");
|
|
||||||
self.$nextTick(function () {
|
|
||||||
self.loaded = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
messageTags() {
|
|
||||||
if (this.loaded) {
|
|
||||||
this.saveTags();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
saveTags: function () {
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
ids: [this.message.ID],
|
|
||||||
tags: this.messageTags
|
|
||||||
}
|
|
||||||
|
|
||||||
self.put('api/v1/tags', data, function (response) {
|
|
||||||
self.scrollInPlace = true;
|
|
||||||
self.$emit('loadMessages');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<tr class="small">
|
|
||||||
<th>Tags</th>
|
|
||||||
<td>
|
|
||||||
<select class="form-select small tag-selector" v-model="messageTags" multiple data-allow-new="true"
|
|
||||||
data-clear-end="true" data-allow-clear="true" data-placeholder="Add tags..."
|
|
||||||
data-badge-style="secondary" data-regex="^([a-zA-Z0-9\-\ \_]){3,}$" data-separator="|,|">
|
|
||||||
<option value="">Type a tag...</option><!-- you need at least one option with the placeholder -->
|
|
||||||
<option v-for="t in existingTags" :value="t">{{ t }}</option>
|
|
||||||
</select>
|
|
||||||
<div class="invalid-feedback">Please select a valid tag.</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
28
server/ui/api/v1/index.html
Normal file
28
server/ui/api/v1/index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Mailpit API v1 documentation</title>
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||||
|
<link rel="icon" href="../../favicon.svg">
|
||||||
|
<script src="../../dist/docs.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<rapi-doc id="thedoc" spec-url="swagger.json" theme="light" layout="column" render-style="read" load-fonts="false"
|
||||||
|
regular-font="system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'"
|
||||||
|
mono-font="Courier New, Courier, System, fixed-width" font-size="large" allow-spec-url-load="false"
|
||||||
|
allow-spec-file-load="false" allow-server-selection="false" allow-search="false" allow-advanced-search="false"
|
||||||
|
bg-color="#ffffff" nav-bg-color="#e3e8ec" nav-text-color="#212529" nav-hover-bg-color="#fff"
|
||||||
|
header-color="#2c3e50" primary-color="#2c3e50" text-color="#212529">
|
||||||
|
<div slot="header">Mailpit API v1 documentation</div>
|
||||||
|
<a target='_blank' href="../../" slot="logo">
|
||||||
|
<img src="../../mailpit.svg" width="40" alt="Mailpit" />
|
||||||
|
</a>
|
||||||
|
</rapi-doc>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
820
server/ui/api/v1/swagger.json
Normal file
820
server/ui/api/v1/swagger.json
Normal file
@ -0,0 +1,820 @@
|
|||||||
|
{
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "OpenAPI 2.0 documentation for [Mailpit](https://github.com/axllent/mailpit).",
|
||||||
|
"title": "Mailpit API",
|
||||||
|
"contact": {
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/axllent/mailpit"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT license",
|
||||||
|
"url": "https://github.com/axllent/mailpit/blob/develop/LICENSE"
|
||||||
|
},
|
||||||
|
"version": "v1"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/info": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns basic runtime information, message totals and latest release version.",
|
||||||
|
"produces": [
|
||||||
|
"application/octet-stream"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"application"
|
||||||
|
],
|
||||||
|
"summary": "Get the application information",
|
||||||
|
"operationId": "AppInformation",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/InfoResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message/{ID}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the summary of a message, marking the message as read.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"summary": "Get message summary",
|
||||||
|
"operationId": "Message",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "message id",
|
||||||
|
"name": "ID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Message",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Message"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message/{ID}/headers": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the message headers as an array.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"summary": "Get message headers",
|
||||||
|
"operationId": "Headers",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "message id",
|
||||||
|
"name": "ID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "MessageHeaders",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/MessageHeaders"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message/{ID}/part/{PartID}": {
|
||||||
|
"get": {
|
||||||
|
"description": "This will return the attachment part using the appropriate Content-Type.",
|
||||||
|
"produces": [
|
||||||
|
"application/*",
|
||||||
|
"image/*",
|
||||||
|
"text/*"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"summary": "Get message attachment",
|
||||||
|
"operationId": "Attachment",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "message id",
|
||||||
|
"name": "ID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "attachment part id",
|
||||||
|
"name": "PartID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/BinaryResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message/{ID}/part/{PartID}/thumb": {
|
||||||
|
"get": {
|
||||||
|
"description": "This will return a cropped 180x120 JPEG thumbnail of an image attachment.\nIf the image is smaller than 180x120 then the image is padded. If the attachment is not an image then a blank image is returned.",
|
||||||
|
"produces": [
|
||||||
|
"image/jpeg"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"summary": "Get an attachment image thumbnail",
|
||||||
|
"operationId": "Thumbnail",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "message id",
|
||||||
|
"name": "ID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "attachment part id",
|
||||||
|
"name": "PartID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/BinaryResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message/{ID}/raw": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the full email source as plain text.",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message"
|
||||||
|
],
|
||||||
|
"summary": "Get message source",
|
||||||
|
"operationId": "Raw",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "message id",
|
||||||
|
"name": "ID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TextResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/messages": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns messages from the mailbox ordered from newest to oldest.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "List messages",
|
||||||
|
"operationId": "GetMessages",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0,
|
||||||
|
"description": "pagination offset",
|
||||||
|
"name": "start",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 50,
|
||||||
|
"description": "limit results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/MessagesSummaryResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"description": "If no IDs are provided then all messages are updated.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "Set read status",
|
||||||
|
"operationId": "SetReadStatus",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Message ids to update",
|
||||||
|
"name": "ids",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"description": "Message ids to update",
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/SetReadStatusRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/OKResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "If no IDs are provided then all messages are deleted.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "Delete messages",
|
||||||
|
"operationId": "Delete",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Message ids to delete",
|
||||||
|
"name": "ids",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"description": "Message ids to delete",
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/DeleteRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/OKResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/search": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the latest messages matching a search.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "Search messages",
|
||||||
|
"operationId": "MessagesSummary",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "search query",
|
||||||
|
"name": "query",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 50,
|
||||||
|
"description": "limit results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/MessagesSummaryResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/tags": {
|
||||||
|
"put": {
|
||||||
|
"description": "To remove all tags from a message, pass an empty tags array.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"tags"
|
||||||
|
],
|
||||||
|
"summary": "Set message tags",
|
||||||
|
"operationId": "SetTags",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Message ids to update",
|
||||||
|
"name": "ids",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"description": "Message ids to update",
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/SetTagsRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/OKResponse"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"$ref": "#/responses/ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"Address": {
|
||||||
|
"description": "An address such as \"Barry Gibbs \u003cbg@example.com\u003e\" is represented\nas Address{Name: \"Barry Gibbs\", Address: \"bg@example.com\"}.",
|
||||||
|
"type": "object",
|
||||||
|
"title": "Address represents a single mail address.",
|
||||||
|
"properties": {
|
||||||
|
"Address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "net/mail"
|
||||||
|
},
|
||||||
|
"AppInformation": {
|
||||||
|
"description": "Response includes the current and latest Mailpit versions, database info, and memory usage",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Database": {
|
||||||
|
"description": "Database path",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"DatabaseSize": {
|
||||||
|
"description": "Database size in bytes",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"LatestVersion": {
|
||||||
|
"description": "Latest Mailpit version",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Memory": {
|
||||||
|
"description": "Current memory usage in bytes",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"Messages": {
|
||||||
|
"description": "Total number of messages in the database",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"Version": {
|
||||||
|
"description": "Current Mailpit version",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "appInformation",
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
},
|
||||||
|
"Attachment": {
|
||||||
|
"description": "Attachment struct for inline and attachments",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ContentID": {
|
||||||
|
"description": "content id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ContentType": {
|
||||||
|
"description": "content type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"FileName": {
|
||||||
|
"description": "file name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"PartID": {
|
||||||
|
"description": "attachment part id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Size": {
|
||||||
|
"description": "size in bytes",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/storage"
|
||||||
|
},
|
||||||
|
"DeleteRequest": {
|
||||||
|
"description": "Delete request",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ids": {
|
||||||
|
"description": "ids\nin:body",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "IDs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "deleteRequest",
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
},
|
||||||
|
"Message": {
|
||||||
|
"description": "Message data excluding physical attachments",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Attachments": {
|
||||||
|
"description": "Message attachments",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Attachment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Bcc": {
|
||||||
|
"description": "Bcc addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cc": {
|
||||||
|
"description": "Cc addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Date": {
|
||||||
|
"description": "Message date if set, else date received",
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"From": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
},
|
||||||
|
"HTML": {
|
||||||
|
"description": "Message body HTML",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ID": {
|
||||||
|
"description": "Unique message database id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Inline": {
|
||||||
|
"description": "Inline message attachments",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Attachment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Read": {
|
||||||
|
"description": "Read status",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"Size": {
|
||||||
|
"description": "Message size in bytes",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"Subject": {
|
||||||
|
"description": "Message subject",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Tags": {
|
||||||
|
"description": "Message tags",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Text": {
|
||||||
|
"description": "Message body text",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"To": {
|
||||||
|
"description": "To addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/storage"
|
||||||
|
},
|
||||||
|
"MessageHeaders": {
|
||||||
|
"description": "Message headers",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "messageHeaders",
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
},
|
||||||
|
"MessageSummary": {
|
||||||
|
"description": "MessageSummary struct for frontend messages",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Attachments": {
|
||||||
|
"description": "Whether the message has any attachments",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"Bcc": {
|
||||||
|
"description": "Bcc addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cc": {
|
||||||
|
"description": "Cc addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Created": {
|
||||||
|
"description": "Created time",
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"From": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
},
|
||||||
|
"ID": {
|
||||||
|
"description": "Unique message database id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Read": {
|
||||||
|
"description": "Read status",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"Size": {
|
||||||
|
"description": "Message size in bytes (total)",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"Subject": {
|
||||||
|
"description": "Email subject",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Tags": {
|
||||||
|
"description": "Message tags",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"To": {
|
||||||
|
"description": "To address",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/storage"
|
||||||
|
},
|
||||||
|
"MessagesSummary": {
|
||||||
|
"description": "MessagesSummary is a summary of a list of messages",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"count": {
|
||||||
|
"description": "Number of results returned",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Count"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"description": "Messages summary\nin:body",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/MessageSummary"
|
||||||
|
},
|
||||||
|
"x-go-name": "Messages"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"description": "Pagination offset",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Start"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"description": "All current tags",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Tags"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"description": "Total number of messages in mailbox",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Total"
|
||||||
|
},
|
||||||
|
"unread": {
|
||||||
|
"description": "Total number of unread messages in mailbox",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Unread"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
},
|
||||||
|
"SetReadStatusRequest": {
|
||||||
|
"description": "Set read status request",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ids": {
|
||||||
|
"description": "ids\nin:body",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "IDs"
|
||||||
|
},
|
||||||
|
"read": {
|
||||||
|
"description": "Read status",
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Read"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "setReadStatusRequest",
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
},
|
||||||
|
"SetTagsRequest": {
|
||||||
|
"description": "Set tags request",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ids": {
|
||||||
|
"description": "ids\nin:body",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "IDs"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"description": "Tags\nin:body",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Tags"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-name": "setTagsRequest",
|
||||||
|
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"BinaryResponse": {
|
||||||
|
"description": "Binary data reponse inherits the attachment's content type"
|
||||||
|
},
|
||||||
|
"ErrorResponse": {
|
||||||
|
"description": "Error reponse"
|
||||||
|
},
|
||||||
|
"InfoResponse": {
|
||||||
|
"description": "Application information",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/AppInformation"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"Body": {
|
||||||
|
"description": "Application information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MessagesSummaryResponse": {
|
||||||
|
"description": "Message summary",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/MessagesSummary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"OKResponse": {
|
||||||
|
"description": "Plain text \"ok\" reponse"
|
||||||
|
},
|
||||||
|
"TextResponse": {
|
||||||
|
"description": "Plain text response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,45 +7,81 @@ import (
|
|||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message struct for loading messages. It does not include physical attachments.
|
// Message data excluding physical attachments
|
||||||
|
//
|
||||||
|
// swagger:model Message
|
||||||
type Message struct {
|
type Message struct {
|
||||||
ID string
|
// Unique message database id
|
||||||
Read bool
|
ID string
|
||||||
From *mail.Address
|
// Read status
|
||||||
To []*mail.Address
|
Read bool
|
||||||
Cc []*mail.Address
|
// From address
|
||||||
Bcc []*mail.Address
|
From *mail.Address
|
||||||
Subject string
|
// To addresses
|
||||||
Date time.Time
|
To []*mail.Address
|
||||||
Tags []string
|
// Cc addresses
|
||||||
Text string
|
Cc []*mail.Address
|
||||||
HTML string
|
// Bcc addresses
|
||||||
Size int
|
Bcc []*mail.Address
|
||||||
Inline []Attachment
|
// Message subject
|
||||||
|
Subject string
|
||||||
|
// Message date if set, else date received
|
||||||
|
Date time.Time
|
||||||
|
// Message tags
|
||||||
|
Tags []string
|
||||||
|
// Message body text
|
||||||
|
Text string
|
||||||
|
// Message body HTML
|
||||||
|
HTML string
|
||||||
|
// Message size in bytes
|
||||||
|
Size int
|
||||||
|
// Inline message attachments
|
||||||
|
Inline []Attachment
|
||||||
|
// Message attachments
|
||||||
Attachments []Attachment
|
Attachments []Attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachment struct for inline and attachments
|
// Attachment struct for inline and attachments
|
||||||
|
//
|
||||||
|
// swagger:model Attachment
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
PartID string
|
// attachment part id
|
||||||
FileName string
|
PartID string
|
||||||
|
// file name
|
||||||
|
FileName string
|
||||||
|
// content type
|
||||||
ContentType string
|
ContentType string
|
||||||
ContentID string
|
// content id
|
||||||
Size int
|
ContentID string
|
||||||
|
// size in bytes
|
||||||
|
Size int
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageSummary struct for frontend messages
|
// MessageSummary struct for frontend messages
|
||||||
|
//
|
||||||
|
// swagger:model MessageSummary
|
||||||
type MessageSummary struct {
|
type MessageSummary struct {
|
||||||
ID string
|
// Unique message database id
|
||||||
Read bool
|
ID string
|
||||||
From *mail.Address
|
// Read status
|
||||||
To []*mail.Address
|
Read bool
|
||||||
Cc []*mail.Address
|
// From address
|
||||||
Bcc []*mail.Address
|
From *mail.Address
|
||||||
Subject string
|
// To address
|
||||||
Created time.Time
|
To []*mail.Address
|
||||||
Tags []string
|
// Cc addresses
|
||||||
Size int
|
Cc []*mail.Address
|
||||||
|
// Bcc addresses
|
||||||
|
Bcc []*mail.Address
|
||||||
|
// Email subject
|
||||||
|
Subject string
|
||||||
|
// Created time
|
||||||
|
Created time.Time
|
||||||
|
// Message tags
|
||||||
|
Tags []string
|
||||||
|
// Message size in bytes (total)
|
||||||
|
Size int
|
||||||
|
// Whether the message has any attachments
|
||||||
Attachments int
|
Attachments int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user