logo
Tutorial

Build a Serverless Application to Automate Invoice Processing on AWS

Learn how to use Amazon Textract and AWS Lambda to process invoice images and extract metadata using the Go programming language.

Published Jun 13, 2023

Amazon Textract is a machine learning service that automatically extracts text, handwriting, and data from scanned documents. It goes beyond simple optical character recognition (OCR) to identify, understand, and extract data from forms and tables. It helps add document text detection and analysis to applications which help businesses automate their document processing workflows and reduce manual data entry, which can save time, reduce errors, and increase productivity.

In this tutorial, you will learn how to build a Serverless solution for invoice processing using Amazon Textract, AWS Lambda and the Go programming language. We will cover how to:

We will be using the following Go libraries:

Application overview

Here is how the application works:

  1. Invoice receipt images uploaded to Amazon S3 trigger a Lambda function.
  2. The Lambda function extracts invoice metadata (such as ID, date, amount) and saves it to an Amazon DynamoDB table.

Before starting this tutorial, you will need the following:

✅ AWS Level
100 - Beginner
⏱ Time to complete
20 minutes
💰 Cost to complete
Free when using the AWS Free Tier
💻 Code Sample
Code sample used in tutorial on GitHub
📢 Feedback
Any feedback, issues, or just a 👍 / ğŸ‘Ž ?
⏰ Last Updated
2023-06-13

Clone the project and change to the right directory:

1
2
3
git clone https://github.com/build-on-aws/amazon-textract-lambda-golang-example

cd amazon-textract-lambda-golang-example

AWS CDK is a framework that lets you define your cloud infrastructure as code in one of its supported programming and provision it through AWS CloudFormation.

To start the deployment, invoke the cdk deploy command. You will see a list of resources that will be created and will need to provide your confirmation to proceed.

1
2
3
4
5
6
7
8
9
10
11
12
13
cd cdk

cdk deploy

# output

Bundling asset TextractInvoiceProcessingGolangStack/textract-function/Code/Stage...

✨ Synthesis time: 5.26

//.... omitted

Do you wish to deploy these changes (y/n)? y

Enter y to start creating the AWS resources required for the application.

If you want to see the AWS CloudFormation template which will be used behind the scenes, run cdk synth and check the cdk.out folder

You can keep track of the stack creation progress in the terminal or navigate to AWS console: CloudFormation > Stacks > TextractInvoiceProcessingGolangStack.

Once the stack creation is complete, you should have:

  • A S3 bucket - Source bucket to upload images.
  • A Lambda function to process invoice images using Amazon Textract.
  • A DynamoDB table to store the invoice data for each image.
  • And a few other components (like IAM roles etc.)

You will also see the following output in the terminal (resource names will differ in your case) - these are the names of the S3 buckets created by CDK:

1
2
3
4
5
6
7
8
✅ TextractInvoiceProcessingGolangStack

✨ Deployment time: 113.51s

Outputs:
TextractInvoiceProcessingGolangStack.invoiceinputbucketname = textractinvoiceprocessin-invoiceimagesinputbucket-bro1y13pib0r
TextractInvoiceProcessingGolangStack.invoiceoutputtablename = textractinvoiceprocessin-invoiceimagesinputbucket-bro1y13pib0r_invoice_output
.....

You are ready to verify the solution.

To try the solution, you can either use an image of your own or use the sample files provided in the GitHub repository which has a few sample invoices. Use the AWS CLI to upload files:

1
2
3
4
5
6
export SOURCE_BUCKET=<enter source S3 bucket name from the CDK output>

aws s3 cp ./invoice1.jpeg s3://$SOURCE_BUCKET

# verify that the file was uploaded
aws s3 ls s3://$SOURCE_BUCKET

This Lambda function will extract invoice data (ID, total amount etc.) from the image and store them in a DynamoDB table.

Upload other files:

1
2
3
4
export SOURCE_BUCKET=<enter source S3 bucket name - check the CDK output>

aws s3 cp ./invoice2.jpeg s3://$SOURCE_BUCKET
aws s3 cp ./invoice3.jpeg s3://$SOURCE_BUCKET

Check the DynamoDB table in the AWS console - you should see the extracted invoice information.

DynamoDB table output

DynamoDB table is designed with source file name as the partition key. This allows you to retrieve all invoice data for a given image.

You can use the AWS CLI to query the DynamoDB table:

1
aws dynamodb scan --table-name <enter table name - check the CDK output>

Here is a quick overview of the Lambda function logic. Please note that some code (error handling, logging etc.) has been omitted for brevity since we only want to focus on the important parts.

1
2
3
4
5
6
7
8
9
func handler(ctx context.Context, s3Event events.S3Event) {
for _, record := range s3Event.Records {

sourceBucketName := record.S3.Bucket.Name
fileName := record.S3.Object.Key

err := invoiceProcessing(sourceBucketName, fileName)
}
}

The Lambda function is triggered when an invoice image is uploaded to the source bucket. The function iterates through the list of invoices and calls the invoiceProcessing function for each invoice.

Let's go through the invoiceProcessing function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func invoiceProcessing(sourceBucketName, fileName string) error {

resp, err := textractClient.AnalyzeExpense(context.Background(), &textract.AnalyzeExpenseInput{
Document: &types.Document{
S3Object: &types.S3Object{
Bucket: &sourceBucketName,
Name: &fileName,
},
},
})

for _, doc := range resp.ExpenseDocuments {
item := make(map[string]ddbTypes.AttributeValue)
item["source_file"] = &ddbTypes.AttributeValueMemberS{Value: fileName}

for _, summaryField := range doc.SummaryFields {

if *summaryField.Type.Text == "INVOICE_RECEIPT_ID" {
item["receipt_id"] = &ddbTypes.AttributeValueMemberS{Value: *summaryField.ValueDetection.Text}
} else if *summaryField.Type.Text == "TOTAL" {
item["total"] = &ddbTypes.AttributeValueMemberS{Value: *summaryField.ValueDetection.Text}
} else if *summaryField.Type.Text == "INVOICE_RECEIPT_DATE" {
item["receipt_date"] = &ddbTypes.AttributeValueMemberS{Value: *summaryField.ValueDetection.Text}
} else if *summaryField.Type.Text == "DUE_DATE" {
item["due_date"] = &ddbTypes.AttributeValueMemberS{Value: *summaryField.ValueDetection.Text}
}
}

_, err := dynamodbClient.PutItem(context.Background(), &dynamodb.PutItemInput{
TableName: aws.String(table),
Item: item,
})
}

return nil
}
  • The invoiceProcessing function calls the Amazon Textract AnalyzeExpense API to extract the invoice data.
  • The function then iterates through the list of ExpenseDocuments and extracts information from specific fields - INVOICE_RECEIPT_ID, TOTAL, INVOICE_RECEIPT_DATE, DUE_DATE.
  • It then saves the extracted invoice metadata in the DynamoDB table.

Once you're done, to delete all the services, simply use:

1
2
3
4
5
cdk destroy

#output prompt (choose 'y' to continue)

Are you sure you want to delete: RekognitionLabelDetectionGolangStack (y/n)?

In this tutorial, you used AWS CDK to deploy a Go Lambda function to process invoice images using Amazon Textract and store the results in a DynamoDB table.