{"id":3033,"date":"2026-08-09T08:27:51","date_gmt":"2026-08-09T12:27:51","guid":{"rendered":"https:\/\/csimmons.dev\/blog\/?p=3033"},"modified":"2026-08-09T08:27:51","modified_gmt":"2026-08-09T12:27:51","slug":"start-stop-mailpit-with-commandbox-server-start-stop","status":"publish","type":"post","link":"https:\/\/csimmons.dev\/blog\/2026\/08\/start-stop-mailpit-with-commandbox-server-start-stop\/","title":{"rendered":"Start\/Stop Mailpit with CommandBox Server Start\/Stop"},"content":{"rendered":"<p>I use <a href=\"https:\/\/mailpit.axllent.org\/\">Mailpit<\/a> to capture email while developing CFML applications locally. It works great, but I don&#8217;t necessarily need it running all the time. Since I already use CommandBox to start and stop my local ColdFusion server, I wanted Mailpit to follow the same lifecycle.<\/p>\n<p>Fortunately, CommandBox server scripts make this straightforward. When the CommandBox server starts, it can start Mailpit. When the server stops, it can stop Mailpit as well.<\/p>\n<h2>Prerequisites<\/h2>\n<p>This example is for macOS and assumes Mailpit was installed with Homebrew:<\/p>\n<pre class=\"wp-block-code\"><code>brew install mailpit<\/code><\/pre>\n<p>You can test Mailpit manually with:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-bash\">brew services start mailpit\nbrew services stop mailpit<\/code><\/pre>\n<p>By default, Mailpit provides:<\/p>\n<ul>\n<li>A web interface at <a href=\"http:\/\/localhost:8025\">http:\/\/localhost:8025<\/a><\/li>\n<li>An SMTP server on <code>localhost:1025<\/code><\/li>\n<li>A readiness endpoint at <a href=\"http:\/\/localhost:8025\/readyz\">http:\/\/localhost:8025\/readyz<\/a><\/li>\n<\/ul>\n<h2>CommandBox server lifecycle scripts<\/h2>\n<p>CommandBox supports server-specific scripts in <code>server.json<\/code>. Among the available lifecycle events are:<\/p>\n<ul>\n<li><code>onServerStart<\/code>, which runs while the server is starting<\/li>\n<li><code>onServerStop<\/code>, which runs before the server stops<\/li>\n<\/ul>\n<p>Add the following top-level <code>scripts<\/code> object to the CommandBox server configuration:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-json\">{\n    \"scripts\": {\n        \"onServerStart\": \"!brew services start mailpit\",\n        \"onServerStop\": \"!brew services stop mailpit\"\n    }\n}<\/code><\/pre>\n<p>The <code>!<\/code> prefix tells CommandBox to execute a native operating-system command.<\/p>\n<p>If CommandBox cannot find Homebrew in its <code>PATH<\/code>, use the full path to the executable. On an Apple Silicon Mac, that will commonly be <code>\/opt\/homebrew\/bin\/brew<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-json\">{\n    \"scripts\": {\n        \"onServerStart\": \"!\/opt\/homebrew\/bin\/brew services start mailpit\",\n        \"onServerStop\": \"!\/opt\/homebrew\/bin\/brew services stop mailpit\"\n    }\n}<\/code><\/pre>\n<p>On an Intel Mac, Homebrew is commonly located at <code>\/usr\/local\/bin\/brew<\/code>. Run <code>which brew<\/code> in Terminal to confirm the correct path.<\/p>\n<h2>Wait until Mailpit is ready<\/h2>\n<p>Starting the Homebrew service does not necessarily mean Mailpit is ready to accept requests at that exact instant. Mailpit provides <code>\/readyz<\/code> for checking its readiness. A successful request returns an HTTP <code>200<\/code> response.<\/p>\n<p>We can make the startup script check that endpoint once per second for up to 20 seconds:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-json\">{\n    \"scripts\": {\n        \"onServerStart\": [\n            \"!brew services start mailpit\",\n            \"!for i in {1..20}; do curl -fsS http:\/\/127.0.0.1:8025\/readyz &amp;&amp; exit 0; sleep 1; done; echo 'Mailpit failed to become ready' &gt;&amp;2; exit 1\"\n        ],\n        \"onServerStop\": \"!brew services stop mailpit\"\n    }\n}<\/code><\/pre>\n<p>This script:<\/p>\n<ol>\n<li>Starts Mailpit through Homebrew.<\/li>\n<li>Checks the Mailpit readiness endpoint.<\/li>\n<li>Continues as soon as Mailpit responds successfully.<\/li>\n<li>Returns an error if Mailpit does not become ready within 20 seconds.<\/li>\n<\/ol>\n<p>You can also check Mailpit manually at any time:<\/p>\n<pre class=\"wp-block-code\"><code>curl -fsS http:\/\/127.0.0.1:8025\/readyz<\/code><\/pre>\n<p>To inspect the Homebrew service state instead, run:<\/p>\n<pre class=\"wp-block-code\"><code>brew services info mailpit<\/code><\/pre>\n<h2>Show Mailpit&#8217;s status on a local home page<\/h2>\n<p>My CommandBox server hosts several local applications, so I have a simple home page that links to each application and development resource. I wanted its Mailpit card to show whether Mailpit was online.<\/p>\n<p>When Mailpit is stopped, the card displays a red <strong>Offline<\/strong> badge:<\/p>\n<figure id=\"attachment_3035\" aria-describedby=\"caption-attachment-3035\" style=\"width: 1390px\" class=\"wp-caption alignnone\"><img width=\"1400\" height=\"600\" data-public-id=\"mailpit-offline\/mailpit-offline.png\" loading=\"lazy\" decoding=\"async\" class=\"wp-post-3033 wp-image-3035 size-full\" src=\"https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_1400,h_600,c_scale\/f_auto,q_auto\/v1786277840\/mailpit-offline\/mailpit-offline.png?_i=AA\" alt=\"Mailpit Offline\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1786277840\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/ccsimmons\/images\/f_auto,q_auto\/v1786277840\/mailpit-offline\/mailpit-offline.png?_i=AA 1400w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_300,h_129,c_scale\/f_auto,q_auto\/v1786277840\/mailpit-offline\/mailpit-offline.png?_i=AA 300w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_1024,h_439,c_scale\/f_auto,q_auto\/v1786277840\/mailpit-offline\/mailpit-offline.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_768,h_329,c_scale\/f_auto,q_auto\/v1786277840\/mailpit-offline\/mailpit-offline.png?_i=AA 768w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption id=\"caption-attachment-3035\" class=\"wp-caption-text\">Mailpit Offline<\/figcaption><\/figure>\n<figure class=\"wp-block-image\"><\/figure>\n<p>When Mailpit is available, it displays a green <strong>Online<\/strong> badge:<\/p>\n<figure id=\"attachment_3036\" aria-describedby=\"caption-attachment-3036\" style=\"width: 1390px\" class=\"wp-caption alignnone\"><img width=\"1400\" height=\"600\" data-public-id=\"mailpit-online\/mailpit-online.png\" loading=\"lazy\" decoding=\"async\" class=\"wp-post-3033 wp-image-3036 size-full\" src=\"https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_1400,h_600,c_scale\/f_auto,q_auto\/v1786277844\/mailpit-online\/mailpit-online.png?_i=AA\" alt=\"Mailpit Online\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1786277844\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/ccsimmons\/images\/f_auto,q_auto\/v1786277844\/mailpit-online\/mailpit-online.png?_i=AA 1400w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_300,h_129,c_scale\/f_auto,q_auto\/v1786277844\/mailpit-online\/mailpit-online.png?_i=AA 300w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_1024,h_439,c_scale\/f_auto,q_auto\/v1786277844\/mailpit-online\/mailpit-online.png?_i=AA 1024w, https:\/\/res.cloudinary.com\/ccsimmons\/images\/w_768,h_329,c_scale\/f_auto,q_auto\/v1786277844\/mailpit-online\/mailpit-online.png?_i=AA 768w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption id=\"caption-attachment-3036\" class=\"wp-caption-text\">Mailpit Online<\/figcaption><\/figure>\n<figure class=\"wp-block-image\"><\/figure>\n<p>First, check the readiness endpoint near the beginning of the CFML page, before the HTML output:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-cfml\">&lt;cfset mailpitOnline = false&gt;\n\n&lt;cftry&gt;\n    &lt;cfhttp\n        url=\"http:\/\/127.0.0.1:8025\/readyz\"\n        method=\"GET\"\n        timeout=\"2\"\n        result=\"mailpitHealth\"&gt;\n\n    &lt;cfset mailpitOnline = val(mailpitHealth.statusCode) eq 200&gt;\n\n    &lt;cfcatch type=\"any\"&gt;\n        &lt;cfset mailpitOnline = false&gt;\n    &lt;\/cfcatch&gt;\n&lt;\/cftry&gt;<\/code><\/pre>\n<p>The default value is <code>false<\/code>. If Mailpit responds with HTTP status <code>200<\/code>, it changes to <code>true<\/code>. A timeout or connection error is caught so an unavailable Mailpit service does not cause the home page itself to fail.<\/p>\n<p>The two-second timeout also prevents the check from delaying the page for too long.<\/p>\n<p>Next, use that value to set the card&#8217;s border and status badge:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-cfml\">&lt;div class=\"col-6 col-md-4 col-lg-3\"&gt;\n    &lt;a href=\"http:\/\/localhost:8025\" class=\"text-decoration-none\" target=\"_blank\"&gt;\n        &lt;div class=\"card h-100 shadow-sm &lt;cfif mailpitOnline&gt;border-success&lt;cfelse&gt;border-danger&lt;\/cfif&gt;\"&gt;\n            &lt;div class=\"card-body text-center\"&gt;\n                &lt;h5 class=\"card-title text-secondary\"&gt;\n                    Mailpit\n                    &lt;cfif mailpitOnline&gt;\n                        &lt;span class=\"badge text-bg-success align-middle\"\n                              style=\"font-size: 0.6rem;\"&gt;Online&lt;\/span&gt;\n                    &lt;cfelse&gt;\n                        &lt;span class=\"badge text-bg-danger align-middle\"\n                              style=\"font-size: 0.6rem;\"&gt;Offline&lt;\/span&gt;\n                    &lt;\/cfif&gt;\n                &lt;\/h5&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/a&gt;\n&lt;\/div&gt;<\/code><\/pre>\n<p>This example uses Bootstrap 5 classes for the card, border, and badge styling.<\/p>\n<h2>Putting it all together<\/h2>\n<p>The final CommandBox configuration is:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-json\">\"scripts\": {\n    \"onServerStart\": [\n        \"!brew services start mailpit\",\n        \"!for i in {1..20}; do curl -fsS http:\/\/127.0.0.1:8025\/readyz &amp;&amp; exit 0; sleep 1; done; echo 'Mailpit failed to become ready' &gt;&amp;2; exit 1\"\n    ],\n    \"onServerStop\": \"!brew services stop mailpit\"\n}<\/code><\/pre>\n<p>Now Mailpit starts along with the CommandBox server, its readiness is verified, and the local home page indicates whether it is online. When the CommandBox server is stopped normally, Mailpit is stopped as well.<\/p>\n<h2>A few things to keep in mind<\/h2>\n<ul>\n<li>The stop hook runs when the server is stopped through CommandBox. It cannot run if CommandBox is force-terminated or the computer shuts down unexpectedly.<\/li>\n<li>Homebrew services are user-wide. If several CommandBox servers or other applications share the same Mailpit instance, stopping one server could stop Mailpit while something else is using it.<\/li>\n<li>If one CommandBox server hosts several applications, put the scripts in that server&#8217;s shared configuration. Mailpit will then follow the lifecycle of the entire server rather than any single application.<\/li>\n<li>The status badge reflects Mailpit&#8217;s state when the CFML page is rendered. Refresh the page to update it.<\/li>\n<\/ul>\n<p>That&#8217;s it. Mailpit is available when the local development server is running and gets out of the way when development is finished.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I use Mailpit to capture email while developing CFML applications locally. It works great, but I don&#8217;t necessarily need it running all the time. Since I already use CommandBox to start and stop my local ColdFusion server, I wanted Mailpit to follow the same lifecycle. Fortunately, CommandBox server scripts make this straightforward. When the CommandBox &#8230; <a title=\"Start\/Stop Mailpit with CommandBox Server Start\/Stop\" class=\"read-more\" href=\"https:\/\/csimmons.dev\/blog\/2026\/08\/start-stop-mailpit-with-commandbox-server-start-stop\/\" aria-label=\"Read more about Start\/Stop Mailpit with CommandBox Server Start\/Stop\">Read more<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"_cloudinary_featured_overwrite":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[68],"tags":[48,52,12,78],"class_list":["post-3033","post","type-post","status-publish","format-standard","hentry","category-developer","tag-coldfusion","tag-commandbox","tag-developer","tag-mailpit"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pbVg43-MV","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/posts\/3033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/comments?post=3033"}],"version-history":[{"count":1,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/posts\/3033\/revisions"}],"predecessor-version":[{"id":3038,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/posts\/3033\/revisions\/3038"}],"wp:attachment":[{"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/media?parent=3033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/categories?post=3033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/csimmons.dev\/blog\/wp-json\/wp\/v2\/tags?post=3033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}