# Newsletter signups in Go (GoCommerce module)

> Collect newsletter signups in a GoCommerce store: each address with its source and date, an unsubscribe route, and a CSV export. It sends no mail itself.

- Canonical: https://kitcommerce.store/integrations/newsletter/
- Last updated: 2026-10-28

---

Marketing

## Newsletter signups in Go

A Go package for the signup box: who asked to hear from the store, and who asked to stop. It keeps each address with where it came from and when, lists and exports them for an operator, and unsubscribes by token. It sends nothing; in its own words, that is a marketing tool’s job.

- **Marketing** module
- **3** settings
- **4** tests
- **7** API operations
- **ext/newsletter**

### What it does

In the package’s words, “a subscription is an email address and a consent.” The storefront posts the address to POST /x/newsletter/subscribe, with an optional name and a source such as footer. The store keeps it lower-cased, with the date, and answers with the status alone: “an address and a token are not a storefront’s to show.”

An operator reads the list on the Newsletter screen, types in an address for a customer who asked at the counter, removes one on request, and exports the list as CSV. The package says it plainly: “Nothing here sends a newsletter: that is a marketing tool’s job, and the export is how the list reaches one.”

Each address gets a random token, and the unsubscribe route takes that token rather than the address, so a forwarded link cannot be edited into somebody else’s. Signing up again after leaving puts the same row back to subscribed.

### Configuration

Nothing to configure in Go: Config has no fields. The Plugins screen holds the three lines of copy a storefront’s signup box can show.

*Newsletter module settings — 3 settings, 0 required*

| Setting | Environment variable | Required | What it does |
| --- | --- | --- | --- |
| `heading` Signup heading | — | No | “Get the news” by default. Public, for the storefront to read. Panel only. |
| `blurb` Signup text | — | No | “New arrivals and offers, now and then. Unsubscribe any time.” by default. Public. Panel only. |
| `thanks` After signing up | — | No | “Thanks — you’re on the list.” by default. Public. Panel only. |

The module reads its Config struct, not the environment. The variable names are the ones the package’s own example or the reference binary uses; in your own `main()` you choose where each value comes from. Where a setting has a panel label, the admin’s settings drawer can hold it too, and a value typed there wins over Config.

### Setting it up

1. **Install the module** — Pass newsletter.New to gocommerce.New, as below, or start the reference binary with -newsletter. It adds one table, and the plugin starts switched on.
2. **Post the signup** — The storefront posts email, name and source to POST /x/newsletter/subscribe, with a hidden website field that people leave empty.
3. **Export for the mailer** — Export CSV on the Newsletter screen, or GET /api/admin/x/newsletter/subscriptions.csv, downloads the subscribed addresses with name, source and dates.
4. **Unsubscribe by token** — GET /x/newsletter/unsubscribe?token=…, or a POST with the token in the body, marks the address unsubscribed. The token lives in the table only; see the limits.

main.go

```
import (
	"github.com/itswadesh/gocommerce/core"
	"github.com/itswadesh/gocommerce/ext/newsletter"
)

app, err := gocommerce.New(cfg, newsletter.New(newsletter.Config{}))
```

The package doc has no example of its own; this is how the reference binary’s -newsletter flag and examples/store/main.go install it. cfg is your gocommerce.Config. Import path `github.com/itswadesh/gocommerce/ext/newsletter`.

### How it works

- **Twice is once**

  An address is unique per store. The same address posted again changes nothing and gets the same answer; a name given the second time replaces the first. After an unsubscribe, a new signup re-subscribes the row and records its new source.

- **A honeypot for bots**

  A hidden website field. A request that fills it is answered 201 subscribed, and nothing is kept.

- **A quiet unsubscribe**

  A token nobody has, or a link clicked twice, gets the same 200. In the code’s words, nothing about the list is learnable from this route.

- **An export a mailer can read**

  email, name, source, status, subscribed_at and unsubscribed_at, one address per row, in a file named newsletter-\<date>.csv. Subscribed addresses only, unless ?status=unsubscribed asks for the others.

- **Rights of its own**

  newsletter.read, which managers and staff hold by default, shows the list. newsletter.write, given to no role by default, adds and removes. The export needs data.export.

- **Masked where it must be**

  On a demo store or for an agent key, addresses in the list and the export are masked, and search matches the name only, so an address cannot be read back one character at a time.

### What it does not do

Read these before an order depends on it. No store is known to run GoCommerce in production yet, so these come from the code, not from anyone’s experience.

- **It sends no mail** — No newsletter, no welcome and no confirmation. The list reaches a mailing tool only through the CSV export or the admin list.
- **No double opt-in** — An address is subscribed on the first POST, with no confirming mail, so an address typed by somebody else is on the list. What the row records is the source and the date.
- **The token is not handed out** — No route returns an address’s unsubscribe token, and the export has no token column. A mailer working from the export cannot build the unsubscribe link the plugin’s description says every mail should carry; the token has to be read from the newsletter_subscriptions table.
- **Opening the link is enough** — GET /x/newsletter/unsubscribe changes the status. There is no confirmation step.
- **Nothing comes back from the mailer** — There is no import and no route for a mailing tool to call. An address that unsubscribes inside the mailing tool stays subscribed here until an operator removes it or the store’s own link is used.
- **Removing is deleting** — Removing an address deletes its row, and the record of when it signed up and left goes with it. Switching the plugin off closes the signup only: unsubscribing and the admin routes keep working.

FAQ

### Questions about the Newsletter module

**Does it send newsletters?**

No. It collects signups and unsubscribes and keeps the list. Sending is left to a mailing tool, which gets the list from the CSV export.

**What is in the export?**

One row per address, with email, name, source, status, subscribed_at and unsubscribed_at, the dates in RFC 3339 and UTC. By default only subscribed addresses; ?status=unsubscribed gives the ones who left.

**How does a shopper unsubscribe?**

Through a link to /x/newsletter/unsubscribe carrying their token, opened or posted. The token is created at signup and kept in the store’s table; no route returns it, so building the link means reading it from the database.

**Are bot signups kept?**

Not those that fill the hidden website field: they are told they are subscribed and nothing is stored. Other bots are not stopped by the module.

### Related modules

Each is a directory under `ext/`, imported the same way. Where this one fits in the store: [What modules add to a direct-to-consumer store](https://kitcommerce.store/solutions/d2c/).

- [Product reviews in Go Ratings and reviews on a GoCommerce product page: held for moderation, marked verified from the store’s own orders, and moved in or out as a CSV file.](https://kitcommerce.store/integrations/reviews/)
- [Product video showcases in Go Put a store’s own YouTube and Facebook videos beside the products they show: no video hosting, a live label the store sets, and counts down to orders.](https://kitcommerce.store/integrations/showcases/)
- [XML sitemaps for a Go store Publish a storefront sitemap from the live GoCommerce catalogue: products, collections and CMS pages, the storefront URL and paths, and the 50,000-URL limit.](https://kitcommerce.store/integrations/sitemaps/)
- [Ecommerce wishlist in Go Let GoCommerce shoppers save products to a list kept by a token, guests included, and see which wished-for products are out of stock. Settings and limits.](https://kitcommerce.store/integrations/wishlist/)

### Source

Everything on this page is read from [`ext/newsletter`](https://github.com/itswadesh/gocommerce/tree/main/ext/newsletter) in the GoCommerce repository, MIT licensed. When this page and the code disagree, the code is right and this page is out of date.

- [ext/newsletter on GitHub](https://github.com/itswadesh/gocommerce/tree/main/ext/newsletter)
- [All GoCommerce modules](https://kitcommerce.store/integrations/)

### Try it against a store of your own

The backend + admin quick start runs GoCommerce’s API and admin on your own machine, without a storefront. Add this module to it and try it on test orders before a real one depends on it.

[Backend + admin quick start](https://kitcommerce.store/#one-command) · [Read the module](https://github.com/itswadesh/gocommerce/tree/main/ext/newsletter) · [All integrations](https://kitcommerce.store/integrations/)
