net/smtp is frozen. gomail is abandoned.
The Go team won't add features to net/smtp. gomail is unmaintained. Gmail rejects the default EHLO. MIME multipart is a nightmare. Ark sends email over HTTP—one struct, one function call, done.
Go's email story is stuck in the past
net/smtp is frozen (no new features accepted). It has no DKIM, no attachments, no timeouts by default. Gmail rejects smtp.SendMail() because it sends "localhost" as the EHLO hostname. gomail was the answer, but it's abandoned. You end up piecing together mime/multipart, hunting for the right fork, and debugging TLS handshakes.
- ×net/smtp is frozen—Go team won't add DKIM, attachments, or timeouts
- ×Gmail rejects default EHLO hostname ("localhost")—requires manual smtp.Client workaround
- ×gomail is abandoned—must find the "right" fork (go-mail/mail) or face unmaintained code
- ×MIME multipart/mixed + multipart/alternative for HTML+text+attachments is brutal
HTTP API instead of SMTP
Ark sends email over HTTPS. No SMTP ports, no TLS version debugging, no EHLO hostnames, no MIME structure. One struct, one function call, proper Go error handling.
Replace net/smtp in 2 minutes
go get, set one environment variable, send. No smtp.Dial, no PlainAuth, no mime/multipart.
Install the module
Add to your Go module. Works with Go 1.21+.
go get github.com/ArkHQ-io/ark-goSet your API key
One environment variable. No SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS.
export ARK_API_KEY=ark_live_xxxxxxSend email
One struct, one function call. Returns message ID, returns error on failure (no silent drops).
package main
import (
"context"
"log"
"os"
"github.com/ArkHQ-io/ark-go"
)
func main() {
client := ark.NewClient(ark.WithAPIKey(os.Getenv("ARK_API_KEY")))
// One struct, one call. Compare to net/smtp boilerplate.
resp, err := client.Emails.Send(context.Background(), ark.EmailSendParams{
From: "[email protected]",
To: []string{"[email protected]"},
Subject: "Welcome!",
HTML: ark.String("<h1>You're in.</h1>"),
Text: ark.String("You're in."), // Optional plaintext
})
if err != nil {
log.Fatal(err) // Real error, not silent failure
}
log.Printf("Sent: %s", resp.Data.ID)
}Code examples
Copy and paste to get started quickly.
package main
import (
"encoding/json"
"net/http"
"os"
"github.com/ArkHQ-io/ark-go"
)
// Goroutine-safe: share one client across all handlers
var arkClient = ark.NewClient(ark.WithAPIKey(os.Getenv("ARK_API_KEY")))
func signupHandler(w http.ResponseWriter, r *http.Request) {
var req struct {
Email string `json:"email"`
Name string `json:"name"`
}
json.NewDecoder(r.Body).Decode(&req)
// Context from request = automatic timeout/cancellation
_, err := arkClient.Emails.Send(r.Context(), ark.EmailSendParams{
From: "[email protected]",
To: []string{req.Email},
Subject: "Welcome, " + req.Name + "!",
HTML: ark.String("<h1>You're in!</h1>"),
})
if err != nil {
// Real error handling—not silent SMTP failure
http.Error(w, "email failed", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]bool{"sent": true})
}package main
import (
"context"
"log"
"sync"
"github.com/ArkHQ-io/ark-go"
)
// Send to thousands of users concurrently
// Compare to: one SMTP connection, serial sends, 2-5s per email
func sendBulkEmails(client *ark.Client, users []User) error {
var wg sync.WaitGroup
errCh := make(chan error, len(users))
// Ark is goroutine-safe—fire hundreds of concurrent requests
for _, user := range users {
wg.Add(1)
go func(u User) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := client.Emails.Send(ctx, ark.EmailSendParams{
From: "[email protected]",
To: []string{u.Email},
Subject: "Your weekly digest",
HTML: ark.String(renderDigest(u)),
})
if err != nil {
errCh <- fmt.Errorf("%s: %w", u.Email, err)
}
}(user)
}
wg.Wait()
close(errCh)
// Collect any errors
for err := range errCh {
log.Printf("Failed: %v", err)
}
return nil
}package main
import (
"context"
"encoding/base64"
"os"
"github.com/ArkHQ-io/ark-go"
)
func sendWithAttachment(client *ark.Client, to, pdfPath string) error {
// Read file
data, err := os.ReadFile(pdfPath)
if err != nil {
return err
}
// Send with attachment—no mime/multipart, no content-type headers
_, err = client.Emails.Send(context.Background(), ark.EmailSendParams{
From: "[email protected]",
To: []string{to},
Subject: "Your invoice is ready",
HTML: ark.String("<p>Invoice attached.</p>"),
Attachments: []ark.Attachment{
{
Filename: "invoice.pdf",
Content: base64.StdEncoding.EncodeToString(data),
Type: "application/pdf",
},
},
})
return err
}What you can build
Escape net/smtp boilerplate
No more smtp.Dial, smtp.Client, PlainAuth, Hello(), Mail(), Rcpt(), Data(), Close(). One struct, one function call.
Concurrent bulk sends
Fire hundreds of goroutines at once. Each HTTP request is independent—no shared SMTP connection bottleneck.
Attachments without MIME pain
Pass attachments as a slice. No mime/multipart.Writer, no Content-Type headers, no boundary strings.
Microservices and CLI tools
Lightweight HTTP client initializes instantly. Works in API servers, background workers, and command-line tools.
Why developers choose Ark
No SMTP configuration
One API key replaces SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, TLS version, and EHLO hostname.
context.Context support
Pass context for timeouts and cancellation. net/smtp has no timeout support by default (hardcoded 10s).
Goroutine-safe client
Share one client across all goroutines. No connection pooling to manage, no mutex needed.
Proper Go error handling
Returns error on failure. No silent SMTP drops, no unchecked response codes.
What you get with Ark
Frequently asked questions
Why not just use net/smtp?
net/smtp is frozen—the Go team won't add DKIM, attachments, or configurable timeouts. Gmail rejects the default EHLO hostname. You end up writing 50+ lines of boilerplate for basic email. Ark is 5 lines.
What about gomail?
The original go-gomail/gomail is abandoned. There's an actively maintained fork (go-mail/mail), but you're still dealing with SMTP configuration, TLS debugging, and the same Gmail EHLO issues. HTTP APIs don't have these problems.
How do I send HTML + plaintext + attachments?
With net/smtp, you need mime/multipart with nested multipart/mixed and multipart/alternative structures. With Ark, pass HTML, Text, and Attachments as fields in one struct.
Is it safe for concurrent use?
Yes. The Ark client is goroutine-safe. Create one client and share it across all goroutines. Each Send() is an independent HTTP request.
Does it support context cancellation?
Yes. All methods accept context.Context. Cancel the context and the request aborts. Set a deadline and it times out. net/smtp has no context support.
What does it cost?
$0.50 per 1,000 emails. No monthly fees. $2.50 welcome credit (5,000 emails). Most Go apps run for months on the free credit.
go get github.com/ArkHQ-io/ark-go
No net/smtp. No gomail. No MIME multipart. 2 minutes to first email.