Skip to content
Open
12 changes: 11 additions & 1 deletion apps/api/.env.dev.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,14 @@ CLIENT_URL=http://localhost:5173

# For monitoring
GRAFANA_URL=http://grafana:3000
MONITORING_DISCORD_WEBHOOK=
MONITORING_DISCORD_WEBHOOK=

# AWS SES (email sending)
AWS_ACCESS_KEY=
AWS_ACCESS_KEY_SECRET=
AWS_REGION=us-east-1
EMAIL_TEMPLATE_DIRECTORY="../../internal/emailutils/templates/"

# Optional: route SES at the local LocalStack emulator instead of real AWS,
# for end-to-end email testing. Requires: docker compose up localstack
# AWS_ENDPOINT_URL=http://localstack:4566
8 changes: 8 additions & 0 deletions apps/api/internal/domains/email/campaign_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ func (s *EmailCampaignService) UpdateCampaignStatus(
return nil, ErrEmailCampaignSentAtRequired
}

// A draft has no send time, so unscheduling clears it. The handler cannot do
// this itself: ScheduledAt is a *time.Time, so an omitted field and an explicit
// null both arrive as nil and it has to assume "leave it alone".
if params.Status == sqlc.EmailCampaignStatusDraft {
params.ScheduledAtDoUpdate = true
params.ScheduledAt = nil
}

return s.emailCampaignRepo.UpdateEmailCampaignStatus(ctx, params)
}

Expand Down
6 changes: 4 additions & 2 deletions apps/api/internal/domains/email/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,14 @@ func (s *EmailService) SendHtmlEmail(recipient string, subject string, templateD

template, err := template.ParseFiles(templateFilePath)
if err != nil {
s.logger.Err(err).Msg("Failed to parse email template for recipient")
s.logger.Err(err).Str("Template", templateFilePath).Msg("Failed to parse email template for recipient")
return err
}

err = template.Execute(&body, templateData)
if err != nil {
s.logger.Err(err).Msg("Failed to inject template variables for recipient '%s'.")
s.logger.Err(err).Str("Template", templateFilePath).Msg("Failed to inject template variables for recipient")
return err
}

err = s.SESClient.SendHTMLEmail([]string{recipient}, "SwampHacks <contact@swamphacks.com>", subject, body.String())
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ services:
retries: 5
start_period: 5s

# Local SES emulator for end-to-end email testing.
# Set AWS_ENDPOINT_URL=http://localstack:4566 in apps/api/.env.dev to use it.
localstack:
image: localstack/localstack:3.8
container_name: localstack
ports:
- "4566:4566"
environment:
- SERVICES=ses

# loki:
# image: grafana/loki:3.7.1
# container_name: loki
Expand Down