Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Basic Event Support #78

Merged
merged 2 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ dist

# VS Code
.vscode

# Intellij
.idea
53 changes: 33 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ SendGrid-Mock serves as a simple server mocking the sendgrid-apis for developmen
* Send mails `POST /v3/mail/send`

* Retrieve sent mails `GET /api/mails`
* Filter capabilities are included and can be combined:
* **To**: `GET /api/mails?to=email@address.com`
* **Subject**:
* `GET /api/mails?subject=The subject` (*exact match*)
* `GET /api/mails?subject=%subject%` (*contains*)
* **Datetime**: `GET /api/mails?dateTimeSince=2020-12-06T10:00:00Z` (*[ISO-8601 format](https://en.wikipedia.org/wiki/ISO_8601)*)
* Filter capabilities are included and can be combined:
* **To**: `GET /api/mails?to=email@address.com`
* **Subject**:
* `GET /api/mails?subject=The subject` (*exact match*)
* `GET /api/mails?subject=%subject%` (*contains*)
* **Datetime**: `GET /api/mails?dateTimeSince=2020-12-06T10:00:00Z` (
*[ISO-8601 format](https://en.wikipedia.org/wiki/ISO_8601)*)

* Delete sent mails `DELETE /api/mails`
* Filter capabilities are included and can be combined:
* **To**: `DELETE /api/mails?to=email@address.com`
* Filter capabilities are included and can be combined:
* **To**: `DELETE /api/mails?to=email@address.com`

### UI

Expand All @@ -29,21 +30,31 @@ SendGrid-Mock serves as a simple server mocking the sendgrid-apis for developmen

### Extras

* Basic authentication support: Add basic authentication credentials by specifying environment variable `AUTHENTICATION` to the following format: `user1:passwordForUser1;user2:passwordForUser2`
* Basic authentication support: Add basic authentication credentials by specifying environment variable `AUTHENTICATION`
to the following format: `user1:passwordForUser1;user2:passwordForUser2`

* Request rate limiting: Both the actual SendGrid API server as well as the SSL server can be rate limited by specifying environment variables:
* `RATE_LIMIT_ENABLED`: `true` or `false` (default)
* `RATE_LIMIT_WINDOW_IN_MS`: The time window in milliseconds (default: `60000`)
* `RATE_LIMIT_MAX_REQUESTS`: The maximum number of requests allowed in the time window (default: `100`)
* `SSL_RATE_LIMIT_ENABLED`: `true` or `false` (default)
* `SSL_RATE_LIMIT_WINDOW_IN_MS`: The time window in milliseconds (default: `60000`)
* `SSL_RATE_LIMIT_MAX_REQUESTS`: The maximum number of requests allowed in the time window (default: `100`)
* Request rate limiting: Both the actual SendGrid API server as well as the SSL server can be rate limited by specifying
environment variables:
* `RATE_LIMIT_ENABLED`: `true` or `false` (default)
* `RATE_LIMIT_WINDOW_IN_MS`: The time window in milliseconds (default: `60000`)
* `RATE_LIMIT_MAX_REQUESTS`: The maximum number of requests allowed in the time window (default: `100`)
* `SSL_RATE_LIMIT_ENABLED`: `true` or `false` (default)
* `SSL_RATE_LIMIT_WINDOW_IN_MS`: The time window in milliseconds (default: `60000`)
* `SSL_RATE_LIMIT_MAX_REQUESTS`: The maximum number of requests allowed in the time window (default: `100`)

* By default, all emails older than 24 hours will be deleted. This can be configured using environment variable `MAIL_HISTORY_DURATION` which uses [ISO-8601 Duration format](https://en.wikipedia.org/wiki/ISO_8601#Durations) such as *'PT24H'*.
* By default, all emails older than 24 hours will be deleted. This can be configured using environment
variable `MAIL_HISTORY_DURATION` which
uses [ISO-8601 Duration format](https://en.wikipedia.org/wiki/ISO_8601#Durations) such as *'PT24H'*.

* Event support: Add basic [event](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event#events)
support by specifying the environment variable `EVENT_DELIVERY_URL`. When set,
[delivered](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event#delivered) events will be
sent to the specified webhook URL when an email is sent.

## Dockerized

The SendGrid-Mock server and the UI are both contained in the same docker-image which you can pull from [Docker Hub](https://cloud.docker.com/u/ghashange/repository/docker/ghashange/sendgrid-mock) and start it via:
The SendGrid-Mock server and the UI are both contained in the same docker-image which you can pull
from [Docker Hub](https://cloud.docker.com/u/ghashange/repository/docker/ghashange/sendgrid-mock) and start it via:

```shell
docker run -it -p 3000:3000 -e "API_KEY=sendgrid-api-key" ghashange/sendgrid-mock:1.9.2
Expand All @@ -63,7 +74,8 @@ docker run -it -p 3000:3000 -e "API_KEY=sendgrid-api-key" -e "CERT_DOMAINNAMES=[

## Development

Setup with `npm ci` and start both server and UI concurrently with `npm run dev`. Per default the server is reachable via <http://localhost:3000> and the UI via <http://localhost:1234>.
Setup with `npm ci` and start both server and UI concurrently with `npm run dev`. Per default the server is reachable
via <http://localhost:3000> and the UI via <http://localhost:1234>.

Some prepared HTTP calls can be found [here](./http-calls).

Expand All @@ -81,4 +93,5 @@ Create docker image with `docker build -t ghashange/sendgrid-mock:1.9.2 .`.

3. Merge PR

4. Create GitHub release and update [Docker Hub description](https://hub.docker.com/repository/docker/ghashange/sendgrid-mock)
4. Create GitHub release and
update [Docker Hub description](https://hub.docker.com/repository/docker/ghashange/sendgrid-mock)
6 changes: 6 additions & 0 deletions __mocks__/axios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const axios = {
post: jest.fn(() => Promise.resolve({ data: {} })),
// Add other axios methods you use here
};

module.exports = axios;
92 changes: 83 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"license": "MIT",
"dependencies": {
"axios": "^1.7.2",
"body-parser": "^1.20.1",
"express": "^4.19.2",
"express-basic-auth": "^1.2.0",
Expand Down
5 changes: 3 additions & 2 deletions src/server/RequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ const RequestHandler = (app, apiAuthenticationKey, mailHandler) => {
const reqApiKey = req.headers.authorization;

if (reqApiKey === `Bearer ${apiAuthenticationKey}`) {
const messageId = crypto.randomUUID();

mailHandler.addMail(req.body);
mailHandler.addMail(req.body, messageId);

res.status(202).header({
'X-Message-ID': crypto.randomUUID(),
'X-Message-ID': messageId,
}).send();
} else {
res.status(403).send({
Expand Down
30 changes: 29 additions & 1 deletion src/server/handler/MailHandler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const {loggerFactory} = require('../logger/log4js');

const axios = require('axios');
const crypto = require('crypto');

const logger = loggerFactory('MailHandler');

const mailWithTimestamp = (mail) => {
Expand Down Expand Up @@ -123,7 +126,7 @@ class MailHandler {
.slice(paginationStart, paginationStart + paginationSize);
}

addMail(mail) {
addMail(mail, messageId = crypto.randomUUID()) {

this.#mails = [mailWithTimestamp(mail), ...this.#mails];

Expand All @@ -132,9 +135,34 @@ class MailHandler {
return Date.parse(mail.datetime).valueOf() >= maxRetentionTime;
});

if (process.env.EVENT_DELIVERY_URL) {
this.sendDeliveryEvents(mail, messageId);
}

logMemoryUsage(this.#mails);
}

sendDeliveryEvents(mail, messageId) {
const datetime = new Date();
const deliveredEvents = mail.personalizations
.flatMap(personalization => personalization.to)
.map(to => {
let event = {
email: to.email,
timestamp: datetime.getTime(),
event: 'delivered',
sg_event_id: crypto.randomUUID(),
sg_message_id: messageId,
};
event['smtp-id'] = crypto.randomUUID();
return event;
});

axios.post(process.env.EVENT_DELIVERY_URL, deliveredEvents)
.then(() => logger.debug(`Delivery events sent successfully to ${process.env.EVENT_DELIVERY_URL}`))
.catch((error) => logger.debug(`Failed to send delivery events to ${process.env.EVENT_DELIVERY_URL}`, error));
}

clear(filterCriteria) {

const filters = [
Expand Down
Loading
Loading