Send email with Go through Volanea by connecting a Go SMTP client to your authenticated sending domain. This guide uses a standard SMTP integration rather than an invented provider-specific Go SDK, so the same approach works cleanly in a Go service, worker, CLI, or serverless job.

What you will build

You will create a small Go program that sends one transactional email through Volanea’s SMTP relay. The program installs a maintained Go mail dependency, reads all credentials and message addresses from environment variables, creates both plain-text and HTML versions of the message, and fails with a useful error if SMTP delivery cannot begin.

The completed integration has four moving parts:

  1. A verified sender address on a domain you control.
  2. Volanea SMTP connection details and a credential that is permitted to send email.
  3. Environment variables that keep the credential out of source control.
  4. A Go program that composes and delivers the message over a TLS-protected SMTP connection.

SMTP is useful when you want a stable, standards-based sending interface. It is also a good fit when your application already has a mail abstraction, when you need to switch providers without rewriting a REST client, or when your infrastructure has a straightforward outbound SMTP configuration. The email message itself still needs a valid sender, recipient, subject, and body; SMTP simply provides the transport from your application to Volanea’s sending infrastructure.

This is a transactional sending example. Use it for application-triggered messages such as a sign-in alert, a password reset, an account verification, an invoice receipt, or a product notification. Do not use a transactional endpoint as a substitute for consent-based bulk campaign sending. Transactional and promotional traffic have different operational requirements, recipient expectations, and suppression handling.

Prerequisites before you send email with Go

Before running the code, prepare the account and domain side of the integration. The application can connect successfully and still have a message rejected if the sender identity is not authorized, the recipient address is malformed, or the SMTP credentials are incorrect.

You need:

  • A current version of Go with module support.
  • A Volanea account with SMTP sending enabled for your project.
  • An API key or SMTP credential that Volanea accepts for authenticated SMTP sending.
  • The SMTP hostname, port, and username assigned to your Volanea account.
  • A sender address on a domain that has been added and authenticated for sending.
  • A recipient address that you control while testing.

Do not guess the hostname, port, username, or authentication method. Copy those values from the current Volanea email API reference or your account’s SMTP configuration. Providers can support more than one TLS mode or credential format, and a plausible-looking value is not a replacement for the value assigned to your project. Keep the email API reference and setup guides nearby when configuring the environment variables below.

Authenticate the sender domain first

A From address is not merely display text. It represents the domain identity that receiving mailbox providers evaluate. Configure the DNS records Volanea provides for your domain before moving a production sender into code. This typically includes domain-authentication records that allow recipient systems to evaluate whether Volanea is authorized to send on behalf of the domain.

Use an address such as notifications@yourdomain.com or support@yourdomain.com only after its parent domain is authenticated in Volanea. Avoid testing with a consumer mailbox address as the sender. Even if a message appears to send, spoofed or unaligned sender identities can hurt deliverability or be rejected by recipient systems.

Keep credentials server-side

Your Volanea API key is a secret. Never send it to a browser, embed it in a mobile application, commit it to Git, put it in a client-side .env file, or print it in application logs. The Go process that sends the email should receive the credential through its runtime environment, deployment secret store, or another server-side configuration mechanism.

For local work, you can export values in your shell. For production, use the secret manager or encrypted configuration system supplied by your hosting environment. The code below deliberately reads variables at runtime instead of placing any credential in main.go.

Install the Go dependency

This guide uses github.com/wneessen/go-mail, a Go package for composing messages and delivering them through SMTP. It handles MIME message construction, SMTP authentication, TLS policies, and context-aware delivery without requiring a Volanea-specific SDK.

Create a new project directory, initialize a Go module, and install the dependency:

mkdir volanea-go-email
cd volanea-go-email
go mod init example.com/volanea-go-email
go get github.com/wneessen/go-mail

The exact dependency installation command is:

go get github.com/wneessen/go-mail

Go records the dependency in go.mod and its checksums in go.sum. Commit both files with your application code. Do not manually edit go.sum, and do not use go get to install a provider-specific package unless that package is documented and maintained by the provider. This example intentionally uses the standard SMTP path supported by Volanea rather than assuming an SDK method name that may not exist.

Configure environment variables

Set the following variables before running the program. The names are local to this example; they are not dashboard labels or required Volanea field names. They make the program portable across local development, CI, and production environments.

export VOLANEA_SMTP_HOST="your-volanea-smtp-host"
export VOLANEA_SMTP_PORT="587"
export VOLANEA_SMTP_USERNAME="your-smtp-username"
export VOLANEA_API_KEY="your-api-key-or-smtp-password"
export EMAIL_FROM="Acme Notifications <notifications@yourdomain.com>"
export EMAIL_TO="you@example.com"

Replace the placeholder SMTP host, port, and username with the exact values assigned to your Volanea project. Set VOLANEA_API_KEY to the credential accepted as the SMTP password for that configuration. If Volanea provides a dedicated SMTP password instead of an API key for your account, store that value in VOLANEA_API_KEY for this example or rename the variable consistently in your own code.

The example uses port 587, which is commonly associated with SMTP submission and STARTTLS, but you must use the port and TLS configuration specified for your Volanea account. Do not change only the port while leaving a mismatched TLS mode in code. Implicit TLS and STARTTLS are different connection flows.

A local .env file is not automatic in Go

Some JavaScript examples load a .env file automatically through a framework or package. Plain Go does not. The code in this guide reads the operating system environment with os.Getenv, so export the variables in your shell, inject them with your process manager, or load them using your own configuration layer.

If you do use a .env file locally, add it to .gitignore. A safe .env.example can list variable names only:

VOLANEA_SMTP_HOST=
VOLANEA_SMTP_PORT=587
VOLANEA_SMTP_USERNAME=
VOLANEA_API_KEY=
EMAIL_FROM=
EMAIL_TO=

Never put a working API key, SMTP password, or real recipient list in .env.example.

Complete Go example: send one transactional email

Create a file named main.go and paste in the following program. It is complete: it validates required configuration, initializes the SMTP client with credentials from environment variables, composes a multipart message, applies mandatory TLS, uses a timeout-bound context, and sends one transactional email.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	mail "github.com/wneessen/go-mail"
)

func requiredEnv(name string) string {
	value := os.Getenv(name)
	if value == "" {
		log.Fatalf("%s is required", name)
	}
	return value
}

func main() {
	host := requiredEnv("VOLANEA_SMTP_HOST")
	portText := requiredEnv("VOLANEA_SMTP_PORT")
	username := requiredEnv("VOLANEA_SMTP_USERNAME")
	apiKey := requiredEnv("VOLANEA_API_KEY")
	from := requiredEnv("EMAIL_FROM")
	to := requiredEnv("EMAIL_TO")

	port, err := strconv.Atoi(portText)
	if err != nil {
		log.Fatalf("VOLANEA_SMTP_PORT must be a number: %v", err)
	}

	message := mail.NewMsg()
	if err := message.From(from); err != nil {
		log.Fatalf("invalid EMAIL_FROM value: %v", err)
	}
	if err := message.To(to); err != nil {
		log.Fatalf("invalid EMAIL_TO value: %v", err)
	}

	message.Subject("Your Volanea Go test email")
	message.SetBodyString(mail.TypeTextPlain, "Hello from Go. Your Volanea SMTP integration is working.")
	message.AddAlternativeString(mail.TypeTextHTML, `<!doctype html>
<html>
  <body>
    <h1>Hello from Go</h1>
    <p>Your <strong>Volanea SMTP</strong> integration is working.</p>
  </body>
</html>`)

	client, err := mail.NewClient(
		host,
		mail.WithPort(port),
		mail.WithUsername(username),
		mail.WithPassword(apiKey),
		mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover),
		mail.WithTLSPortPolicy(mail.TLSMandatory),
	)
	if err != nil {
		log.Fatalf("create SMTP client: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()

	if err := client.DialAndSendWithContext(ctx, message); err != nil {
		log.Fatalf("send email: %v", err)
	}

	fmt.Println("Email accepted by the SMTP server.")
}

Run it with:

go run .

If the SMTP server accepts the submission, the program prints Email accepted by the SMTP server. That result means the SMTP relay accepted your message for processing. It does not mean a recipient’s mailbox has displayed the message in the inbox. Acceptance, downstream delivery, spam placement, bounces, and user engagement are distinct stages. Treat the submission result as a transport success signal, then use delivery events and logs to understand what happened afterward.

How the Go code works

The example separates configuration, message creation, client creation, and delivery. This structure makes it easier to test each concern and prevents secrets from being embedded in code.

Configuration validation

requiredEnv exits immediately if a required variable is empty. Failing early is preferable to opening a network connection with blank credentials and receiving a vague authentication error. In a larger service, you may prefer to return configuration errors from a constructor instead of calling log.Fatalf, but the principle is the same: validate once during startup.

The SMTP port is text in the environment, so the code converts it to an integer with strconv.Atoi. This catches a common deployment mistake: setting a port like smtp-587, leaving a trailing space in a templated secret, or accidentally using a hostname value where a number is expected.

Message construction and content types

mail.NewMsg() creates the message object. From and To parse and validate the email addresses, while Subject assigns the subject line. The sender can include a display name in the conventional Name <address@example.com> form, as shown in the environment example.

The program sets a text/plain body and an HTML alternative. This is intentional. HTML is useful for layout, buttons, and visual hierarchy, but a plain-text alternative improves accessibility, supports clients that do not render HTML, and provides a readable fallback. The mail library builds the multipart structure and content-type boundaries for you.

Do not set the body to HTML while declaring it plain text, or vice versa. Incorrect MIME content types can produce a message that displays raw tags, fails formatting expectations, or is processed inconsistently by email clients. Let the message library assign the appropriate MIME structure by using mail.TypeTextPlain and mail.TypeTextHTML as in the sample.

SMTP authentication and TLS

WithUsername and WithPassword supply the SMTP credentials. The password comes from VOLANEA_API_KEY, keeping the required secret in an environment variable. SMTPAuthAutoDiscover allows the client to negotiate an SMTP authentication mechanism that the server advertises rather than hard-coding a mechanism without confirming it is supported.

TLSMandatory is a deliberate security choice. It requires the SMTP connection to use TLS rather than silently falling back to unencrypted transport. If your Volanea SMTP configuration uses an implicit-TLS port instead of a STARTTLS submission port, follow the connection mode in the provider’s current documentation and adjust the client settings accordingly. Do not weaken TLS merely to make a connection error disappear.

Context and timeout behavior

The context limits the send attempt to 20 seconds. SMTP involves DNS resolution, TCP connection, TLS negotiation, authentication, and message upload; any one of those stages can stall if a network dependency is unhealthy. Without a timeout, a background worker can accumulate blocked goroutines and eventually exhaust capacity.

The program uses DialAndSendWithContext, so cancellation applies to the connection and sending workflow. In an HTTP handler, derive the context carefully. For a user-facing request, you might enqueue the email job and return after durable queue acceptance instead of keeping the browser request open until SMTP submission completes.

Use this pattern in a real Go application

The example is a command-line program because it is easy to copy, run, and diagnose. In a production application, move the sending logic behind a small interface so business code does not need to know about SMTP variables or message-library types.

A practical shape is:

  • Load SMTP configuration once when the process starts.
  • Construct one reusable mail client where your deployment model permits it.
  • Create a message for each business event.
  • Call a sender method from a background worker or queue consumer.
  • Record your own event identifier and the outcome of each submission attempt.

Avoid opening an SMTP connection inside a database transaction. If an order record is committed but the network call fails, you need a durable retry path. If the email sends before the database transaction rolls back, the customer can receive a receipt for an order that does not exist. A transactional outbox or durable job queue solves this by storing the intent to send alongside the business change and processing that intent after commit.

Keep business events idempotent

Networks can fail after your client uploads a message but before it receives the SMTP server’s final response. From the application’s point of view, the send may be ambiguous: the provider could have accepted it, or it might not have. Blindly retrying every error can send duplicate receipts, reset links, or login alerts.

Design the business event with an idempotency strategy. Store a stable internal event ID, such as an order ID plus an event type, and mark the event as submitted only after you have recorded the outcome. When a retry occurs, check whether the event has already been successfully handed off or whether it needs another attempt. For sensitive messages, use application-level tokens that remain valid across a duplicate send rather than generating a new token on every retry.

Keep message data safe

Treat recipient addresses and template variables as sensitive data. Do not log the full message body in normal production logs, and do not include reset tokens, one-time codes, or API credentials in error reports. Log a minimal event ID, recipient domain when appropriate, message category, timestamp, and the error class.

Validate addresses at the boundary where you collect them, not only at send time. Syntax validation is useful but cannot prove that a mailbox exists or accepts mail. For sign-up and lead flows, you can use the email address verification tool before relying on an address for important notifications.

Common errors when sending through SMTP from Go

This section focuses on issues that commonly occur in a Go SMTP integration. Read the full error text, but do not log the secret value itself. SMTP failures often contain enough protocol context to identify whether the problem is authentication, sender authorization, TLS, recipient validation, or network access.

Authentication failures: 535, invalid credentials, or login denied

An SMTP authentication failure usually means the host, username, or secret does not match the configured Volanea project. Verify that VOLANEA_SMTP_USERNAME contains the exact SMTP username and that VOLANEA_API_KEY contains the intended credential with no surrounding quotes copied into the value.

A frequent mistake is exporting the wrong key from another environment. Test and production credentials are often intentionally isolated. Confirm that the domain, account, and credential belong to the same environment, then restart the process after updating the secret. If you rotated a key, update every worker and deployment that may still be using the previous value.

Sender rejected: unauthorized From address

If the server rejects the sender, compare EMAIL_FROM with the authenticated domain configuration. The domain portion of the sender address must be one that Volanea permits your account to use. A display name does not affect authorization; Acme <notifications@yourdomain.com> is authorized or rejected based on the address and account configuration, not the visible name.

Do not fix this by changing the sender to a personal inbox. Authenticate the actual domain used by your product. Consistent domain alignment supports both recipient trust and long-term deliverability.

Wrong content type or raw HTML appears in the message

This error often comes from hand-building MIME headers or sending HTML as a plain-text body. In the provided code, SetBodyString(mail.TypeTextPlain, ...) creates the text alternative and AddAlternativeString(mail.TypeTextHTML, ...) creates the HTML alternative. Keep those types matched to the content.

If you add attachments or inline images later, continue using the mail library’s message APIs instead of concatenating MIME boundaries yourself. MIME formatting has small but consequential details, including line endings, transfer encodings, multipart boundaries, and header folding.

TLS handshake failures or certificate errors

A TLS error can indicate a hostname typo, outbound network interception, a blocked SMTP port, or a mismatch between the selected port and expected TLS mode. Re-copy the assigned hostname and port from Volanea’s current configuration. Then confirm whether that endpoint expects STARTTLS or an implicit TLS connection.

Do not disable certificate verification or switch to unencrypted SMTP in production to bypass a handshake error. That can expose credentials and message content. Fix the network path or use the TLS configuration documented for the endpoint.

Connection timeouts or connection refused

A timeout means the application could not complete a connection within the configured period. A refusal usually means a host was reached but no service accepted the port. Check DNS resolution from the same runtime environment, firewall egress rules, container network policy, cloud-provider SMTP restrictions, and the port value.

Many cloud environments restrict outbound SMTP to reduce abuse. If this is your situation, request the appropriate egress access or use Volanea’s REST API from the environment where HTTP is available. Do not assume a successful local-machine connection proves that a container, CI runner, or serverless platform can reach the same SMTP endpoint.

context deadline exceeded

The 20-second timeout in the sample is a guardrail. If it occurs occasionally, investigate network latency, DNS availability, SMTP availability, and egress policy. If it occurs consistently, do not simply increase the timeout indefinitely; identify which stage is blocked.

In higher-volume applications, place email work on a queue and use bounded retries with backoff. A retry should not run inside a tight loop. Backoff reduces repeated pressure on a degraded network dependency and gives temporary failures time to recover.

Go has no async or await

JavaScript SMTP and API examples often show await sendEmail(). Go does not have async or await; a normal function call blocks until it returns. In this guide, DialAndSendWithContext runs synchronously and returns an error that you must check.

If your service needs concurrency, run jobs in controlled goroutines or use a worker pool. Do not start an unbounded goroutine for every request. Bound concurrency, pass contexts through the call chain, and ensure errors are collected rather than discarded. A goroutine that sends email without reporting its error can create silent delivery gaps.

Ignoring the returned error

This is the most damaging application-level mistake. The compiler will permit you to discard an error, but your users will not receive a notification if the network, credentials, or SMTP server fail. Always inspect and record send errors.

In the sample, log.Fatalf is appropriate for a one-off CLI test. In an HTTP service, return a wrapped error to the caller or retry system rather than terminating the whole process because one message failed.

Deliverability considerations after SMTP acceptance

Successful SMTP authentication is only the beginning of reliable transactional email. A message should have a clear purpose, a recognizable sender, accurate recipient data, and content that matches the action that triggered it.

For password resets, include a short explanation of why the recipient received the email, an expiration window for the link, and guidance for what to do if they did not request it. For receipts, include the order reference and a support path. For security alerts, identify the relevant event without exposing secrets or unnecessary personal data.

Keep transactional email narrowly relevant. Sending promotional content in a password reset or receipt can increase complaints and reduce trust. If your product sends both marketing and operational mail, separate the message categories in your application model and respect opt-outs and suppression outcomes for the appropriate category.

Monitor the full lifecycle: accepted, delivered, bounced, complained, deferred, and unsubscribed where applicable. Your product database should not keep retrying addresses that have produced permanent failures, and your support team should have enough event context to answer whether an important message was attempted and what happened next.

Next steps

After the first transactional send works, move beyond a single hard-coded message.

First, add webhooks. A webhook endpoint lets your application receive delivery-related events so it can update internal records when a message is accepted, delivered, deferred, bounced, or complained about. Validate webhook signatures using Volanea’s documented verification process, return a fast successful response after safely recording the event, and make your handler idempotent because an event can be retried.

Second, move repeated email markup into templates. Templates reduce duplicated HTML across services and make it easier to keep receipts, alerts, and lifecycle messages consistent. Store only the variables each template needs, validate required variables before sending, and render a test version for real-world inboxes before enabling a production workflow.

Finally, make sending observable. Add structured logs, metrics for submission attempts and failures, alerts for unusual bounce or authentication-error rates, and a delivery dashboard review process. A reliable integration is not just code that can call SMTP; it is code with safe secrets, authenticated domains, controlled retries, useful event handling, and clear evidence of what happened to each important message.

FAQ

Do I need a Volanea-specific Go SDK?

No. This guide uses standard SMTP through a Go mail package, so it does not depend on an undocumented Volanea-specific SDK or fictional method names. Use the SMTP hostname, port, username, and credential assigned to your Volanea account.

Is VOLANEA_API_KEY safe to put in main.go?

No. Keep it in an environment variable or managed secret store. The sample reads VOLANEA_API_KEY at runtime and never includes a real credential in source code.

Does SMTP acceptance mean the email reached the inbox?

No. It means the SMTP server accepted the message for processing. Delivery, mailbox placement, bounces, and complaints are later outcomes that should be tracked through events and operational logs.

Why send both plain-text and HTML email bodies?

A plain-text alternative improves accessibility and provides a fallback for clients that do not render HTML. The HTML version supports richer layout. Sending both is a practical default for transactional messages.

Should I send email directly inside an HTTP request handler?

For low-volume noncritical actions, it can be acceptable if you use a timeout and handle errors. For important or higher-volume workflows, persist an email job or outbox record and send from a background worker so transient SMTP failures do not make the user request slow or unreliable.