Step-by-Step Guide: Creating a 1x1 Pixel with Golang, AWS Lambda, API Gateway

In my recent endeavor of developing a booking app using Lambda, Go, and the Serverless framework, I encountered the need to track when a reminder email was opened. After conducting some research, the most straightforward solution appeared to be utilizing a 1x1 pixel. Fortunately, implementing this solution was relatively simple, especially with Golang’s image package.
Allow me to share the code with you, along with some essential considerations you should keep in mind.
Serverless.yml
The Serverless framework has been a steadfast companion throughout my entire serverless journey, empowering me to craft infrastructure as code and deploy fully functional APIs within minutes. To achieve this seamlessly with the Serverless framework, there are a few key configurations to set up.
Firstly, it’s crucial to enable the binaryMediaTypes option for API Gateway. This ensures that the service treats specific media types as binaries, paving the way for smoother interactions.
Secondly, for those working with Golang, it’s essential to note that the go1.x runtime is deprecated. Instead, opt for the provided.al2 runtime. Additionally, to execute your Golang code successfully, ensure that you name your executable file "bootstrap."
With these considerations in mind, let me share my serverless.yml file.
service: tracking-pixel-golang
frameworkVersion: '3'
provider:
name: aws
runtime: provided.al2
architecture: arm64
versionFunctions: false
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'us-east-1'}
apiGateway:
binaryMediaTypes:
- "*/*"
custom:
deploymentBucket:
name: ${self:service}-${self:provider.stage}-artifacts
serverSideEncryption: AES256
package:
individually: true
functions:
tracking-lambda:
handler: bootstrap
package:
artifact: ./bin/tracking-lambda/tracking-lambda.zip
events:
- http: #v1 => REST API . v2 => HTTP API => httpApi
path: /pixel
method: get
cors: trueLambda Function The Lambda function is surprisingly concise, as its primary function is to return a 1x1 gif. However, even in its simplicity, there are essential considerations to ensure its seamless operation. Firstly, make sure to obtain the AWS Lambda packages for Go.
go get github.com/aws/aws-lambda-go/events github.com/aws/aws-lambda-go/lambda
Second, make sure you send your response in base64 encoding and that you send the IsBase64Encoded key as true. That being said here is the code I used.
package main
import (
"bytes"
"context"
"encoding/base64"
"image"
"image/color"
"image/gif"
"os"
"time"
"log/slog"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
var logHandler = slog.NewTextHandler(os.Stdout, nil).WithAttrs([]slog.Attr{slog.String("pkg", "main")})
var logger = slog.New(logHandler)
func main() {
ctx, c := context.WithTimeout(context.Background(), time.Second*30)
defer c()
lambda.StartWithOptions(HandleRequest, lambda.WithContext(ctx))
}
func HandleRequest(ctx context.Context, event events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
logger.Info("An Email Was Opened")
if v, ok := event.Headers["user-agent"]; ok {
logger.Info("From:", slog.String("user-agent", v))
}
// Email ID
logger.Info("Email ID:", slog.String("email-id", event.QueryStringParameters["email-id"]))
logger.Info("path:", slog.String("path", event.Path))
// 1 x 1 pixel
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
img.Set(0, 0, color.RGBA{255, 0, 0, 255})
var buff bytes.Buffer
if err := gif.Encode(&buff, img, nil); err != nil {
logger.Error("failed to encode pixel.", slog.String("error", err.Error()))
return &events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
IsBase64Encoded: false,
}, nil
}
// reason to send image as base64
/*
API Gateway typically expects data in a text format, and base64 encoding is a convenient way to represent binary data
*/
// out 1x1 pixel as a base64 string.
response := base64.StdEncoding.EncodeToString(buff.Bytes())
return &events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "image/gif",
},
Body: response,
IsBase64Encoded: true,
}, nil
}I hope this helps. Bye 👋
Repository where you can find this code: https://github.com/japb1998/tracking-demo
Stackademic
Thank you for reading until the end. Before you go:






