[GH-ISSUE #21] Provide structs of API v1 responses for use in client code #16

Closed
opened 2026-03-15 12:07:03 +03:00 by kerem · 11 comments
Owner

Originally created by @alfa-alex on GitHub (Oct 21, 2022).
Original GitHub issue: https://github.com/axllent/mailpit/issues/21

Moved from https://github.com/axllent/mailpit/issues/15#issuecomment-1285295908:

The structs used for the API response are currently a bit scattered over the project (like apiv1.MessagesResult and storage.Message or storage.Summary). Is there any chance you could provide these structs in a central place (perhaps apiv1 is the right package already) and allow/encourage them being imported from client projects? That would be extremely handy!

I'm not fully into the internals, but maybe you could do a simple type alias (type MessageSummary = storage.Summary) in the apiv1 package so you don't need to copy them as long as v1 is the latest API. Once you internally switch to something else you could change the type alias to become the struct that resembles whatever the APIv1 returns.

Originally created by @alfa-alex on GitHub (Oct 21, 2022). Original GitHub issue: https://github.com/axllent/mailpit/issues/21 Moved from https://github.com/axllent/mailpit/issues/15#issuecomment-1285295908: The structs used for the API response are currently a bit scattered over the project (like `apiv1.MessagesResult` and `storage.Message` or `storage.Summary`). Is there any chance you could provide these structs in a central place (perhaps `apiv1` is the right package already) and allow/encourage them being imported from client projects? That would be extremely handy! I'm not fully into the internals, but maybe you could do a simple type alias (`type MessageSummary = storage.Summary`) in the `apiv1` package so you don't need to copy them as long as `v1` is the latest API. Once you internally switch to something else you could change the type alias to become the struct that resembles whatever the APIv1 returns.
kerem closed this issue 2026-03-15 12:07:08 +03:00
Author
Owner

@axllent commented on GitHub (Oct 21, 2022):

If I understand correctly, you are using something written in Go to integrate with the API (or other?), and rather than manually (re)define the structs you are simply importing the relative structs used in Mailpit?

I'm definitely not opposed to restructuring the code to make it more logical and/or easier to find, most likely grouping all struct types into one namespace I think (as they are a bit scattered), however it would be useful to me to understand your use case.

<!-- gh-comment-id:1286638480 --> @axllent commented on GitHub (Oct 21, 2022): If I understand correctly, you are using something written in Go to integrate with the API (or other?), and rather than manually (re)define the structs you are simply importing the relative structs used in Mailpit? I'm definitely not opposed to restructuring the code to make it more logical and/or easier to find, most likely grouping all struct types into one namespace I think (as they are a bit scattered), however it would be useful to me to understand your use case.
Author
Owner

@alfa-alex commented on GitHub (Oct 21, 2022):

If I understand correctly, you are using something written in Go to integrate with the API

Yes!

and rather than manually (re)define the structs you are simply importing the relative structs used in Mailpit?

Yes!

<!-- gh-comment-id:1286643139 --> @alfa-alex commented on GitHub (Oct 21, 2022): > If I understand correctly, you are using something written in Go to integrate with the API Yes! > and rather than manually (re)define the structs you are simply importing the relative structs used in Mailpit? Yes!
Author
Owner

@axllent commented on GitHub (Oct 21, 2022):

Moving the structs to apiv1 would lead to an import cycle (apiv1 requires data from storage, and storage requires the struct to parse and return the data from the database), so that's not an option as I reuse the same structure in both the API message summaries and storage (db). I believe they are already grouped correctly based on their internal usage, so I have just looked at your suggestion of aliasing from within the apiv1 package.

To see if I understand your requirements correctly, I have just pushed a branch which I would like you to have a look at please, and let me know if this is what you are after? I did rename a few existing structs to make them more obvious, but the main thing I am referring to here is the server/apiv1/structs.go which contains the structs (and aliases to structs) which are used to return JSON-formatted data.

I do not know if this is in fact useful, and I do not know what the implications of importing an alias is, but I'll let you tell me.

<!-- gh-comment-id:1286738689 --> @axllent commented on GitHub (Oct 21, 2022): Moving the structs to `apiv1` would lead to an import cycle (`apiv1` requires data from `storage`, and `storage` requires the struct to parse and return the data from the database), so that's not an option as I reuse the same structure in both the API message summaries and storage (db). I believe they are already grouped correctly based on their internal usage, so I have just looked at your suggestion of aliasing from within the apiv1 package. To see if I understand your requirements correctly, I have just pushed [a branch](https://github.com/axllent/mailpit/tree/feature/api-structs) which I would like you to have a look at please, and let me know if this is what you are after? I did rename a few existing structs to make them more obvious, but the main thing I am referring to here is the `server/apiv1/structs.go` which contains the structs (and aliases to structs) which are used to return JSON-formatted data. I do not know if this is in fact useful, and I do not know what the implications of importing an alias is, but I'll let you tell me.
Author
Owner

@alfa-alex commented on GitHub (Oct 21, 2022):

This is indeed very useful. Exactly what I was looking for. Thanks for the fast response!

Just as an example of how I am currently using it:

import "github.com/axllent/mailpit/storage"
// ...
httpClient := http.Client{Timeout: 500 * time.Millisecond}
// determine messageID via a call to `api/v1/messages`
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1/message/%s", mailpitHost, messageID), nil)
// ... error checking
res, err := httpClient.Do(req)
// ... error checking
var message storage.Message
err = json.NewDecoder(res.Body).Decode(&message)
// ... error checking
res.Body.Close()

If Message is now aliased in apiv1 I can simply just import github.com/axllent/mailpit/server/apiv1 and access it as apiv1.Message. Should you decide to move the Message struct into another package than storage, as long as you update the alias on your side, I (and any other downstream lib user) would have to change no code at all. I hope this explains the "implications" a bit (otherwise, let me know, please).

The only thing that comes to mind when I review this is that maybe having these types aliases in a package apiv1 in a subfolder of server is not perfectly fitting anymore. If you create these aliases for the sake of letting downstream lib users like me import them in clients, they would probably better be placed in some package in the root directory (which is not named server). I mean it doesn't hurt to import from github.com/axllent/mailpit/server/apiv1, but it somehow doesn't feel right (sounds much too internal, and somehow incorrect if I want to use this for a client).
Unfortunately, there certainly is code in your apiv1 package that is server-only, so we'd best have a new package containing only the aliases / stuff for lib users. However, creating e.g. a new package named apiv1 in the project root would cause a clash of package names. Maybe renaming the /server/apiv1 package to something else would be the solution then. I dunno. Naming is hard.
Just to clarify what I mean:

  • mailpit/<nameOfSomePackageThatLibUsersWouldImportFrom> would contain all the aliases
  • mailpit/server/apiv1 can contain everything it currently contains (except for the aliases you only added for me in the linked branch); this package might be renamed to let nameOfSomePackageThatLibUsersWouldImportFrom be called apiv1 (technically, both could even have the same name - since you're never importing from the upper one internally, that might be a viable option - but I guess it's also a matter of taste)
<!-- gh-comment-id:1287108734 --> @alfa-alex commented on GitHub (Oct 21, 2022): This is indeed very useful. Exactly what I was looking for. Thanks for the fast response! Just as an example of how I am currently using it: ```go import "github.com/axllent/mailpit/storage" // ... httpClient := http.Client{Timeout: 500 * time.Millisecond} // determine messageID via a call to `api/v1/messages` req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1/message/%s", mailpitHost, messageID), nil) // ... error checking res, err := httpClient.Do(req) // ... error checking var message storage.Message err = json.NewDecoder(res.Body).Decode(&message) // ... error checking res.Body.Close() ``` If `Message` is now aliased in `apiv1` I can simply just import `github.com/axllent/mailpit/server/apiv1` and access it as `apiv1.Message`. Should you decide to move the `Message` struct into another package than `storage`, as long as you update the alias on your side, I (and any other downstream lib user) would have to change no code at all. I hope this explains the "implications" a bit (otherwise, let me know, please). The only thing that comes to mind when I review this is that maybe having these types aliases in a package `apiv1` in a subfolder of `server` is not perfectly fitting anymore. If you create these aliases for the sake of letting downstream lib users like me import them in clients, they would probably better be placed in some package in the root directory (which is not named `server`). I mean it doesn't hurt to import from `github.com/axllent/mailpit/server/apiv1`, but it somehow doesn't feel right (sounds much too internal, and somehow incorrect if I want to use this for a client). Unfortunately, there certainly is code in your `apiv1` package that is server-only, so we'd best have a new package containing only the aliases / stuff for lib users. However, creating e.g. a new package named `apiv1` in the project root would cause a clash of package names. Maybe renaming the `/server/apiv1` package to something else would be the solution then. I dunno. Naming is hard. Just to clarify what I mean: - `mailpit/<nameOfSomePackageThatLibUsersWouldImportFrom>` would contain all the aliases - `mailpit/server/apiv1` can contain everything it currently contains (except for the aliases you only added for me in the linked branch); this package *might* be renamed to let `nameOfSomePackageThatLibUsersWouldImportFrom` be called `apiv1` (technically, both could even have the same name - since you're never importing from the upper one internally, that might be a viable option - but I guess it's also a matter of taste)
Author
Owner

@axllent commented on GitHub (Oct 21, 2022):

Thanks for the feedback, much appreciated. Firstly I'm glad this is what you are after. Initially I wasn't too keen on aliasing and tried to restructure the structs, however that backfired when I discovered that the structs were already (as far as I am concerned) logically situated within the codebase, at least from an application point of view. Then aliasing seemed the better solution.

I also understand your point about the relative position of the API structs from an import perspective. There are arguments for and against splitting out that from the API:

  • For: "easier" imports (shorter paths) and potentially easier to find in the code base.
  • Against: yet another subfolder in the root level, either separating the structs from the API server logic or duplicating/aliasing. Also the root-level module would need to include the API version, meaning multiple additional root-level folders if there was ever a v2 API etc (so much more clutter and separation of code)

This ties in somewhat to my comments in #20 ~ Mailpit was never intended to be a library (though your use-case makes perfect sense for API "integration"). After consideration (of the pros and cons) I think that the cons outweigh the pros with regards to splitting out the API structs into a root-level submodule, as it very easily over-complicates things (and this was one of my big "issues" with MailHog ~ over-engineered/complicated). I believe that anyone (such as yourself) who is qualified to build something in Go to integrate with the API won't have too much of a problem finding what you need in this new aliased structure (ie; the changes I already made in the branch), and it this keeps the APIv1 stuff altogether and doesn't complicate things.

So unless you have anything further to add (I'll wait for your feedback), I will merge what I have done so far and run with that, ie: structs staying in github.com/axllent/mailpit/server/apiv1 and stick with that.

<!-- gh-comment-id:1287375746 --> @axllent commented on GitHub (Oct 21, 2022): Thanks for the feedback, much appreciated. Firstly I'm glad this is what you are after. Initially I wasn't too keen on aliasing and tried to restructure the structs, however that backfired when I discovered that the structs were already (as far as I am concerned) logically situated within the codebase, at least from an application point of view. Then aliasing seemed the better solution. I also understand your point about the relative position of the API structs from an import perspective. There are arguments for and against splitting out that from the API: - For: "easier" imports (shorter paths) and potentially easier to find in the code base. - Against: yet another subfolder in the root level, either separating the structs from the API server logic or duplicating/aliasing. Also the root-level module would need to include the API version, meaning multiple additional root-level folders if there was ever a v2 API etc (so much more clutter and separation of code) This ties in somewhat to my comments in #20 ~ Mailpit was never intended to be a library (though your use-case makes perfect sense for API "integration"). After consideration (of the pros and cons) I think that the cons outweigh the pros with regards to splitting out the API structs into a root-level submodule, as it very easily over-complicates things (and this was one of my big "issues" with MailHog ~ over-engineered/complicated). I believe that anyone (such as yourself) who is qualified to build something in Go to integrate with the API won't have too much of a problem finding what you need in this new aliased structure (ie; the changes I already made in the branch), and it this keeps the APIv1 stuff altogether and doesn't complicate things. So unless you have anything further to add (I'll wait for your feedback), I will merge what I have done so far and run with that, ie: structs staying in `github.com/axllent/mailpit/server/apiv1` and stick with that.
Author
Owner

@alfa-alex commented on GitHub (Oct 24, 2022):

Perfectly valid reasoning. Go ahead. 👍 And thank you for all your efforts!

<!-- gh-comment-id:1288534569 --> @alfa-alex commented on GitHub (Oct 24, 2022): Perfectly valid reasoning. Go ahead. :+1: And thank you for all your efforts!
Author
Owner

@axllent commented on GitHub (Oct 24, 2022):

Cool, thanks for the feedback. As per #20, I'll try get the release out by the end of the week which will include these changes.

<!-- gh-comment-id:1289453717 --> @axllent commented on GitHub (Oct 24, 2022): Cool, thanks for the feedback. As per #20, I'll try get the release out by the end of the week which will include these changes.
Author
Owner

@axllent commented on GitHub (Oct 28, 2022):

@alfa-alex This has now been included in v1.2.6 👍

<!-- gh-comment-id:1295492821 --> @axllent commented on GitHub (Oct 28, 2022): @alfa-alex This has now been included in `v1.2.6` :+1:
Author
Owner

@alfa-alex commented on GitHub (Nov 3, 2022):

Awesome! Did the integration, it's all much cleaner now! 👍

Thank you!

<!-- gh-comment-id:1301927467 --> @alfa-alex commented on GitHub (Nov 3, 2022): Awesome! Did the integration, it's all much cleaner now! :+1: Thank you!
Author
Owner

@corey-aloia commented on GitHub (Jan 23, 2024):

@alfa-alex any chance you have open-sourced your rest client somewhere? :)

<!-- gh-comment-id:1905614381 --> @corey-aloia commented on GitHub (Jan 23, 2024): @alfa-alex any chance you have open-sourced your rest client somewhere? :)
Author
Owner

@alfa-alex commented on GitHub (Jan 26, 2024):

@alfa-alex any chance you have open-sourced your rest client somewhere? :)

I'm sorry, but no. We're using mailpit for a specific (internal) scenario that wouldn't make any sense outside of the (closed source) project it's in. Therefore there's little chance it'll be open-sourced (or helpful on its own).

<!-- gh-comment-id:1911660379 --> @alfa-alex commented on GitHub (Jan 26, 2024): > @alfa-alex any chance you have open-sourced your rest client somewhere? :) I'm sorry, but no. We're using mailpit for a specific (internal) scenario that wouldn't make any sense outside of the (closed source) project it's in. Therefore there's little chance it'll be open-sourced (or helpful on its own).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/mailpit#16
No description provided.