client

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

email-service client (Go)

Producer-side client for the Pennsieve email-service. Import it from any Go service that needs to send a platform email.

go get github.com/pennsieve/email-service/client

It builds an EmailRequest and puts it on the email-service SQS queue. The email-service consumer Lambda renders the template (with org branding), delivers via SES, journals the send, and handles idempotency. Producers never touch SES.

Usage

import (
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/sqs"
	emailclient "github.com/pennsieve/email-service/client"
)

cfg, _ := config.LoadDefaultConfig(ctx)
c := emailclient.New(sqs.NewFromConfig(cfg), queueURL) // queueURL from the queue's Terraform output

req := emailclient.DatasetPublicationAccepted(
	emailclient.To{Name: "Alice", Email: "alice@example.com"},
	emailclient.DatasetPublicationAcceptedArgs{
		DatasetName:  "My Dataset",
		ReviewerName: "Bob",
		Date:         "2026-06-18",
	},
).WithOrganization(367) // optional: use the org's branded template

if err := c.Send(ctx, req); err != nil {
	// handle enqueue error
}

API

  • New(sqsClient, queueURL) *Client — construct the client.
  • (*Client).Send(ctx, EmailRequest) error — validate + enqueue.
  • Typed builders — one per template (e.g. DatasetPublicationAccepted, ChangeOfDatasetOwner, AddedToTeam, RehydrationComplete, …) — each takes a To and an Args struct and returns a validated EmailRequest. The builder owns the messageId and the context keys the template expects.
  • Message(messageId, to, context) — untyped escape hatch.
  • Chainable options on EmailRequest: WithOrganization(id), WithDedupeId(id), WithSubject(s).

Builders are generated

messages.go is generated from the template manifest (contract/template-variables.json) — do not hand-edit it. After a template's variables change (and the manifest is updated), regenerate both the Go and Scala builders:

make generate   # = go run internal/gen/main.go

This guarantees the builders never drift from the templates' actual variables.

Notes

  • Send is asynchronous: it returns once the message is on the queue; delivery happens later in the consumer.
  • dedupeId (via WithDedupeId) is recommended when you have a stable id for a logical send; otherwise the consumer dedupes on a content hash.

Documentation

Overview

Package client is the producer-side client for the Pennsieve email-service.

Other services use it to request an email by enqueuing an EmailRequest on the email-service SQS queue. They never call SES directly — the email-service consumer Lambda owns template rendering, SES delivery, journaling, and idempotency. This package only constructs the request and puts it on the queue.

Typical use:

c := client.New(sqs.NewFromConfig(cfg), queueURL)
req := client.DatasetAccepted(
	client.To{Name: "Alice", Email: "alice@example.com"},
	client.DatasetAcceptedArgs{DatasetName: "My Dataset", ReviewerName: "Bob"},
).WithOrganization(367)
err := c.Send(ctx, req)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AddedToOrganizationArgs

type AddedToOrganizationArgs struct {
	Administrator      string
	CustomMessage      string
	Host               string
	OrganizationName   string
	OrganizationNodeId string
}

AddedToOrganizationArgs are the variables for the "added-to-organization" template.

type AddedToTeamArgs

type AddedToTeamArgs struct {
	Administrator      string
	Host               string
	OrganizationNodeId string
	TeamName           string
}

AddedToTeamArgs are the variables for the "added-to-team" template.

type ChangeOfDatasetOwnerArgs

type ChangeOfDatasetOwnerArgs struct {
	DatasetName        string
	DatasetNodeId      string
	Host               string
	OrganizationName   string
	OrganizationNodeId string
	PreviousOwnerName  string
}

ChangeOfDatasetOwnerArgs are the variables for the "change-of-dataset-owner" template.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client enqueues email requests on the email-service SQS queue.

func New

func New(sqsClient SQSAPI, queueURL string) *Client

New constructs a Client. queueURL is the URL of the email-service send queue for the target environment (e.g. from the queue's Terraform output).

func (*Client) Send

func (c *Client) Send(ctx context.Context, req EmailRequest) error

Send validates and enqueues an email request. The email-service consumer then renders the template and delivers via SES. Send returns once the message is on the queue — delivery is asynchronous.

type DatasetEmbargoAcceptedArgs

type DatasetEmbargoAcceptedArgs struct {
	DatasetName string
	EmbargoDate string
}

DatasetEmbargoAcceptedArgs are the variables for the "dataset-embargo-accepted" template.

type DatasetProposalAcceptedArgs

type DatasetProposalAcceptedArgs struct {
	AppURL                 string
	ProposalTitle          string
	WelcomeWorkspaceNodeId string
	WorkspaceName          string
}

DatasetProposalAcceptedArgs are the variables for the "dataset-proposal-accepted" template.

type DatasetProposalRejectedArgs

type DatasetProposalRejectedArgs struct {
	AppURL                 string
	ProposalTitle          string
	WelcomeWorkspaceNodeId string
	WorkspaceName          string
}

DatasetProposalRejectedArgs are the variables for the "dataset-proposal-rejected" template.

type DatasetProposalSubmittedArgs

type DatasetProposalSubmittedArgs struct {
	AppURL          string
	AuthorEmail     string
	AuthorName      string
	ProposalTitle   string
	WorkspaceName   string
	WorkspaceNodeId string
}

DatasetProposalSubmittedArgs are the variables for the "dataset-proposal-submitted" template.

type DatasetProposalWithdrawnArgs

type DatasetProposalWithdrawnArgs struct {
	AppURL          string
	AuthorEmail     string
	AuthorName      string
	ProposalTitle   string
	WorkspaceName   string
	WorkspaceNodeId string
}

DatasetProposalWithdrawnArgs are the variables for the "dataset-proposal-withdrawn" template.

type DatasetPublicationAcceptedArgs

type DatasetPublicationAcceptedArgs struct {
	DatasetName  string
	Date         string
	ReviewerName string
}

DatasetPublicationAcceptedArgs are the variables for the "dataset-publication-accepted" template.

type DatasetPublicationInReviewArgs

type DatasetPublicationInReviewArgs struct {
	DatasetName        string
	DatasetNodeId      string
	Date               string
	Host               string
	OrganizationNodeId string
	OwnerName          string
}

DatasetPublicationInReviewArgs are the variables for the "dataset-publication-in-review" template.

type DatasetRevisionArgs

type DatasetRevisionArgs struct {
	DatasetNodeId      string
	DiscoverDatasetId  string
	DiscoverHost       string
	Host               string
	OrganizationNodeId string
}

DatasetRevisionArgs are the variables for the "dataset-revision" template.

type DatasetRevisionNeededArgs

type DatasetRevisionNeededArgs struct {
	DatasetName        string
	DatasetNodeId      string
	Date               string
	Host               string
	Message            string
	OrganizationNodeId string
	ReviewerName       string
}

DatasetRevisionNeededArgs are the variables for the "dataset-revision-needed" template.

type DatasetSubmittedForReviewArgs

type DatasetSubmittedForReviewArgs struct {
	DatasetName        string
	DatasetNodeId      string
	Date               string
	Host               string
	OrganizationName   string
	OrganizationNodeId string
	OwnerEmailAddress  string
	OwnerName          string
}

DatasetSubmittedForReviewArgs are the variables for the "dataset-submitted-for-review" template.

type EmailAddressChangedArgs

type EmailAddressChangedArgs struct {
	CurrentEmailAddress  string
	PreviousEmailAddress string
	TransactionNumber    string
}

EmailAddressChangedArgs are the variables for the "email-address-changed" template.

type EmailRequest

type EmailRequest struct {
	MessageId  string         `json:"messageId"`
	DedupeId   string         `json:"dedupeId,omitempty"`
	Recipients []Recipient    `json:"recipients"`
	Context    map[string]any `json:"context"`
}

EmailRequest is the JSON payload carried in the body of an SQS message. It is the wire contract between producers (this package) and the email-service consumer: which email to send (MessageId), who to send it to (Recipients), and the name-value pairs applied to the template (Context).

DedupeId is optional. When a producer supplies one, it is used (combined with the recipient) as the idempotency key so a redelivered SQS message does not cause a second send. When absent, a deterministic hash of the request is used instead (see DedupeKey).

func AddedToOrganization

func AddedToOrganization(to To, args AddedToOrganizationArgs) EmailRequest

AddedToOrganization builds the "added-to-organization" email.

func AddedToTeam

func AddedToTeam(to To, args AddedToTeamArgs) EmailRequest

AddedToTeam builds the "added-to-team" email.

func ChangeOfDatasetOwner

func ChangeOfDatasetOwner(to To, args ChangeOfDatasetOwnerArgs) EmailRequest

ChangeOfDatasetOwner builds the "change-of-dataset-owner" email.

func DatasetEmbargoAccepted

func DatasetEmbargoAccepted(to To, args DatasetEmbargoAcceptedArgs) EmailRequest

DatasetEmbargoAccepted builds the "dataset-embargo-accepted" email.

func DatasetProposalAccepted

func DatasetProposalAccepted(to To, args DatasetProposalAcceptedArgs) EmailRequest

DatasetProposalAccepted builds the "dataset-proposal-accepted" email.

func DatasetProposalRejected

func DatasetProposalRejected(to To, args DatasetProposalRejectedArgs) EmailRequest

DatasetProposalRejected builds the "dataset-proposal-rejected" email.

func DatasetProposalSubmitted

func DatasetProposalSubmitted(to To, args DatasetProposalSubmittedArgs) EmailRequest

DatasetProposalSubmitted builds the "dataset-proposal-submitted" email.

func DatasetProposalWithdrawn

func DatasetProposalWithdrawn(to To, args DatasetProposalWithdrawnArgs) EmailRequest

DatasetProposalWithdrawn builds the "dataset-proposal-withdrawn" email.

func DatasetPublicationAccepted

func DatasetPublicationAccepted(to To, args DatasetPublicationAcceptedArgs) EmailRequest

DatasetPublicationAccepted builds the "dataset-publication-accepted" email.

func DatasetPublicationInReview

func DatasetPublicationInReview(to To, args DatasetPublicationInReviewArgs) EmailRequest

DatasetPublicationInReview builds the "dataset-publication-in-review" email.

func DatasetRevision

func DatasetRevision(to To, args DatasetRevisionArgs) EmailRequest

DatasetRevision builds the "dataset-revision" email.

func DatasetRevisionNeeded

func DatasetRevisionNeeded(to To, args DatasetRevisionNeededArgs) EmailRequest

DatasetRevisionNeeded builds the "dataset-revision-needed" email.

func DatasetSubmittedForReview

func DatasetSubmittedForReview(to To, args DatasetSubmittedForReviewArgs) EmailRequest

DatasetSubmittedForReview builds the "dataset-submitted-for-review" email.

func EmailAddressChanged

func EmailAddressChanged(to To, args EmailAddressChangedArgs) EmailRequest

EmailAddressChanged builds the "email-address-changed" email.

func EmbargoAccessApproved

func EmbargoAccessApproved(to To, args EmbargoAccessApprovedArgs) EmailRequest

EmbargoAccessApproved builds the "embargo-access-approved" email.

func EmbargoAccessDenied

func EmbargoAccessDenied(to To, args EmbargoAccessDeniedArgs) EmailRequest

EmbargoAccessDenied builds the "embargo-access-denied" email.

func EmbargoAccessRequested

func EmbargoAccessRequested(to To, args EmbargoAccessRequestedArgs) EmailRequest

EmbargoAccessRequested builds the "embargo-access-requested" email.

func EmbargoDatasetReleaseAccepted

func EmbargoDatasetReleaseAccepted(to To, args EmbargoDatasetReleaseAcceptedArgs) EmailRequest

EmbargoDatasetReleaseAccepted builds the "embargo-dataset-release-accepted" email.

func EmbargoedDatasetReleased

func EmbargoedDatasetReleased(to To, args EmbargoedDatasetReleasedArgs) EmailRequest

EmbargoedDatasetReleased builds the "embargoed-dataset-released" email.

func ErrorPublishing

func ErrorPublishing(to To, args ErrorPublishingArgs) EmailRequest

ErrorPublishing builds the "error-publishing" email.

func InviteExternalExistingUserToDataset

func InviteExternalExistingUserToDataset(to To, args InviteExternalExistingUserToDatasetArgs) EmailRequest

InviteExternalExistingUserToDataset builds the "invite-external-existing-user-to-dataset" email.

func InviteExternalNewUserToDataset

func InviteExternalNewUserToDataset(to To, args InviteExternalNewUserToDatasetArgs) EmailRequest

InviteExternalNewUserToDataset builds the "invite-external-new-user-to-dataset" email.

func Message

func Message(messageId string, to To, context map[string]any) EmailRequest

Message builds a request for an arbitrary messageId with a raw context map. Prefer a typed builder when one exists.

func NotificationOfPublicationToContributor

func NotificationOfPublicationToContributor(to To, args NotificationOfPublicationToContributorArgs) EmailRequest

NotificationOfPublicationToContributor builds the "notification-of-publication-to-contributor" email.

func RehydrationComplete

func RehydrationComplete(to To, args RehydrationCompleteArgs) EmailRequest

RehydrationComplete builds the "rehydration-complete" email.

func RehydrationFailed

func RehydrationFailed(to To, args RehydrationFailedArgs) EmailRequest

RehydrationFailed builds the "rehydration-failed" email.

func (EmailRequest) DedupeKey

func (r EmailRequest) DedupeKey(recipientEmail string) string

DedupeKey returns the idempotency key for sending this request to the given recipient. If the producer supplied a DedupeId, the key is DedupeId+recipient so the same logical send is deduped per recipient. Otherwise the key is a SHA-256 over (messageId, recipient, canonicalized context) so that an identical redelivered message maps to the same key without any producer cooperation. The consumer uses this for its idempotency guard.

func (EmailRequest) OrganizationId

func (r EmailRequest) OrganizationId() (int64, bool)

OrganizationId returns the organization id from the request context, if one is present. The second return value is false when the context has no organizationId, in which case the consumer uses the default (unbranded) template. JSON numbers unmarshal into float64, so we accept that and a few other representations to be forgiving of producers.

func (EmailRequest) Subject

func (r EmailRequest) Subject(defaultSubject string) string

Subject returns the subject line to use for the email. A subject in the request context overrides the default subject from the template mapping.

func (EmailRequest) WithDedupeId

func (r EmailRequest) WithDedupeId(dedupeId string) EmailRequest

WithDedupeId sets an explicit idempotency id. Use it when the producer can supply a stable id for a logical send (preferred over relying on the content hash). Returns the request for chaining.

func (EmailRequest) WithOrganization

func (r EmailRequest) WithOrganization(organizationId int64) EmailRequest

WithOrganization sets the organization id in the context so the consumer can resolve an org-branded template (custom/O{id}/) with fallback to the default. Returns the request for chaining.

func (EmailRequest) WithSubject

func (r EmailRequest) WithSubject(subject string) EmailRequest

WithSubject overrides the template's default subject for this send. Returns the request for chaining.

type EmbargoAccessApprovedArgs

type EmbargoAccessApprovedArgs struct {
	DatasetName       string
	Date              string
	DiscoverDatasetId string
	DiscoverHost      string
	ManagerName       string
}

EmbargoAccessApprovedArgs are the variables for the "embargo-access-approved" template.

type EmbargoAccessDeniedArgs

type EmbargoAccessDeniedArgs struct {
	DatasetName string
	Date        string
	ManagerName string
}

EmbargoAccessDeniedArgs are the variables for the "embargo-access-denied" template.

type EmbargoAccessRequestedArgs

type EmbargoAccessRequestedArgs struct {
	DatasetName        string
	DatasetNodeId      string
	Date               string
	Host               string
	OrganizationNodeId string
	UserName           string
}

EmbargoAccessRequestedArgs are the variables for the "embargo-access-requested" template.

type EmbargoDatasetReleaseAcceptedArgs

type EmbargoDatasetReleaseAcceptedArgs struct {
	DatasetName  string
	Date         string
	ReviewerName string
}

EmbargoDatasetReleaseAcceptedArgs are the variables for the "embargo-dataset-release-accepted" template.

type EmbargoedDatasetReleasedArgs

type EmbargoedDatasetReleasedArgs struct {
	DatasetName       string
	DiscoverDatasetId string
	DiscoverHost      string
}

EmbargoedDatasetReleasedArgs are the variables for the "embargoed-dataset-released" template.

type ErrorPublishingArgs

type ErrorPublishingArgs struct {
	DatasetName string
}

ErrorPublishingArgs are the variables for the "error-publishing" template.

type InviteExternalExistingUserToDatasetArgs

type InviteExternalExistingUserToDatasetArgs struct {
	CustomMessage        string
	DatasetName          string
	InvitingEmailAddress string
	InvitingPerson       string
	WorkspaceName        string
}

InviteExternalExistingUserToDatasetArgs are the variables for the "invite-external-existing-user-to-dataset" template.

type InviteExternalNewUserToDatasetArgs

type InviteExternalNewUserToDatasetArgs struct {
	CustomMessage        string
	DatasetName          string
	InvitingEmailAddress string
	InvitingPerson       string
	SetupAccountLink     string
	WorkspaceName        string
}

InviteExternalNewUserToDatasetArgs are the variables for the "invite-external-new-user-to-dataset" template.

type NotificationOfPublicationToContributorArgs

type NotificationOfPublicationToContributorArgs struct {
	DatasetName       string
	DiscoverDatasetId string
	DiscoverHost      string
	OwnerName         string
}

NotificationOfPublicationToContributorArgs are the variables for the "notification-of-publication-to-contributor" template.

type Recipient

type Recipient struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

Recipient identifies a single destination for an email.

type RehydrationCompleteArgs

type RehydrationCompleteArgs struct {
	AWSRegion           string
	DatasetID           string
	DatasetVersionID    string
	RehydrationLocation string
}

RehydrationCompleteArgs are the variables for the "rehydration-complete" template.

type RehydrationFailedArgs

type RehydrationFailedArgs struct {
	DatasetID           string
	DatasetVersionID    string
	RequestID           string
	SupportEmailAddress string
}

RehydrationFailedArgs are the variables for the "rehydration-failed" template.

type SQSAPI

type SQSAPI interface {
	SendMessage(ctx context.Context, params *sqs.SendMessageInput, optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error)
}

SQSAPI is the subset of the SQS client this package uses. *sqs.Client satisfies it; tests provide a fake.

type To

type To = Recipient

To is an alias used by the builders for readability at call sites.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL