Skip to main content

Migrating from the AWS SDK

If your application already talks to a queue through the AWS SDK, you do not have to rewrite it to move to Rinda. Point the SDK at our endpoint, swap the credentials, and your existing code keeps working.

import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';

const sqs = new SQSClient({
region: 'us-east-1',
endpoint: 'https://sqs.rinda.dev/compat/sqs', // ← added
credentials: {
accessKeyId: process.env.RINDA_ACCESS_KEY_ID!, // ← swapped
secretAccessKey: process.env.RINDA_SECRET_ACCESS_KEY!,
},
});

// Everything below is your existing code, unchanged.
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL!,
MessageBody: JSON.stringify({ orderId: '12345' }),
}));

Consumers built on sqs-consumer, or on your own polling loop, work the same way — they are the AWS SDK underneath.


Before you start: what this credential is​

Read this part. It is the one thing about compatibility that is genuinely different from the rest of Rinda.

An ordinary Rinda API key is stored as a hash. The plaintext exists once, when you create it, and we cannot recover it — if someone walked off with our database they would not have your key.

A compatibility credential cannot work that way. The AWS SDK authenticates by signing each request with your secret, and checking that signature means recomputing it, which means we have to be able to read the secret back.

So it is stored differently: encrypted under a KMS key belonging to your account, not hashed. An attacker would need both our database and the ability to call KMS as our service. That is a real protection, and it is still weaker than "we cannot recover it at all".

Because of that:

  • Compatibility credentials are separate from API keys. Issuing one does not change how your existing keys are stored.
  • They are available on paid plans only. Free accounts share one encryption key, and a shared key would make the protection above mean much less than it sounds.
  • They cannot be used on the Rinda REST API, and your API keys cannot be used on the compatibility endpoint. Two credential types, two doors.
  • An expiry is required, and capped at a year. An API key can live forever; this cannot. A leak of something we can decrypt should expire on its own rather than wait to be noticed.

If you are writing a new application rather than migrating one, use the Rinda SDK and an ordinary API key. This exists so you do not have to rewrite what already works.


Getting a credential​

In the dashboard, open Settings → AWS SDK compatibility. Pick the project, name the credential, choose its scopes, and set an expiry — most migrating applications want:

ScopeWhy
messages:writeSendMessage, DeleteMessage, ChangeMessageVisibility
messages:readReceiveMessage
queues:readGetQueueUrl, GetQueueAttributes

queues:read catches people out: the AWS SDK often resolves a queue name to a URL before it can send anything, and that is a queue read. All three are ticked by default.

The secret is shown once, with a copy button. Put it wherever your application already reads AWS_SECRET_ACCESS_KEY from.

When it expires​

The credential stops working and your application starts getting InvalidClientTokenId. Issue a new one and swap it in — there is no in-place renewal, deliberately, because a credential that can be extended indefinitely is one that never gets rotated.

The dashboard warns when a credential has two weeks or less left.


Queue URLs​

Rinda queue URLs look like this:

https://sqs.rinda.dev/{accountId}/{queueSlug}

Get one from GetQueueUrl, or copy it from the queue's page in the dashboard:

const { QueueUrl } = await sqs.send(new GetQueueUrlCommand({ QueueName: 'orders' }));

Code that does string work on a queue URL — splitting on / to get a name is the usual one — keeps working, because the shape matches.


What is supported​

Everything an application does at run time. Each one is covered by a test suite that drives the real AWS SDK against the endpoint and checks the responses, including the checksums the SDK validates for itself:

OperationSupported
SendMessageYes
SendMessageBatchYes, including partial failure
ReceiveMessageYes, including long polling
DeleteMessageYes
DeleteMessageBatchYes
ChangeMessageVisibilityYes
ChangeMessageVisibilityBatchYes
GetQueueUrlYes
GetQueueAttributesYes

FIFO works as you would expect: MessageGroupId is required on a FIFO queue and rejected on a standard one, exactly as it is on the original.

What is not​

Queue management — CreateQueue, DeleteQueue, SetQueueAttributes, PurgeQueue, tagging, and permissions — is done in the Rinda dashboard.

This is deliberate. Creating a queue here means choosing a plan-limited region, a queue kind, and an environment, and explaining what happens when a limit is reached. A CreateQueue call has nowhere to ask, and nowhere to explain.

Calling one of those returns a clear error naming the operation, not a generic failure:

InvalidAction: Queues are created and configured in the Rinda dashboard,
where plan limits, regions, and queue kinds are enforced with an interface
that can explain them. CreateQueue has no way to ask which environment a queue
belongs to. See https://docs.rinda.dev/guides/aws-sdk-migration for what is
supported.

If your deployment creates queues through the SDK, that part moves to the dashboard or to our REST API. It is usually a one-off script rather than application code.


What you gain, and what it costs​

Being honest about the trade, because you will find out either way.

You gain message introspection — browse, search, and replay any message — along with per-queue retention you control, no IAM policies to write, and one bill.

It costs latency. Rinda sits between your application and the queue engine, so every call has one more network hop than talking to the engine directly — typically a few milliseconds, and more if your application runs far from your queue's region. For most workloads that is well below the noise; if you are chasing single-digit milliseconds on a send, it will not be. Measure it against your own traffic before you commit, and put your application in the same region as the queue.


Errors​

Errors arrive as the typed exceptions your existing code already catches — QueueDoesNotExist, InvalidParameterValue, AccessDenied — because your retry logic, dead-letter handling, and alerting are all written against those.

Two you may not have seen before:

AccessDenied — the signature was valid but the credential lacks the scope. The message names the missing scope. Scopes cannot be widened after issue, so issue a new credential.

InvalidClientTokenId — the credential is unknown, revoked, expired, or the signature did not match. Deliberately one error for all four: telling them apart would help someone guessing.


Moving over​

  1. Issue a credential with the three scopes above, and set an expiry.
  2. Create your queues in the Rinda dashboard, matching the names your application uses.
  3. In a non-production environment, set the endpoint and credentials and run your existing test suite. Nothing else should change.
  4. Compare: send a message, receive it, delete it. Then check the queue's page in the dashboard — the same message is there, with its full history, which is the part you did not have before.
  5. Roll production over one consumer at a time. Both endpoints work independently, so you can move senders and receivers separately.

Running out of a migration part-way is fine. Nothing about the compatibility endpoint is one-way.