@huloglobal/vendure-plugin-email-tracking 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +34 -0
- package/README.md +129 -0
- package/dist/email-log.entity.d.ts +57 -0
- package/dist/email-log.entity.d.ts.map +1 -0
- package/dist/email-log.entity.js +147 -0
- package/dist/email-log.entity.js.map +1 -0
- package/dist/email-tracking.controller.d.ts +51 -0
- package/dist/email-tracking.controller.d.ts.map +1 -0
- package/dist/email-tracking.controller.js +260 -0
- package/dist/email-tracking.controller.js.map +1 -0
- package/dist/email-tracking.service.d.ts +48 -0
- package/dist/email-tracking.service.d.ts.map +1 -0
- package/dist/email-tracking.service.js +196 -0
- package/dist/email-tracking.service.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +43 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +52 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +53 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +118 -0
- package/dist/plugin.js.map +1 -0
- package/dist/proxy-headers.d.ts +37 -0
- package/dist/proxy-headers.d.ts.map +1 -0
- package/dist/proxy-headers.js +81 -0
- package/dist/proxy-headers.js.map +1 -0
- package/dist/tracking-email-sender.d.ts +22 -0
- package/dist/tracking-email-sender.d.ts.map +1 -0
- package/dist/tracking-email-sender.js +117 -0
- package/dist/tracking-email-sender.js.map +1 -0
- package/package.json +53 -0
- package/ui/components/email-log.component.ts +372 -0
- package/ui/email-log-nav.module.ts +24 -0
- package/ui/email-log.module.ts +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@huloglobal/vendure-plugin-email-tracking` are documented
|
|
4
|
+
here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
5
|
+
and this project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — Unreleased
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `EmailTrackingPlugin` — wraps `@vendure/email-plugin` and persists every
|
|
11
|
+
send to the `email_log` table.
|
|
12
|
+
- `TrackingEmailSender` — drop-in `EmailSender` replacement that wraps
|
|
13
|
+
the default Nodemailer sender and injects an open-tracking pixel and a
|
|
14
|
+
click redirector into the outgoing HTML.
|
|
15
|
+
- `EmailTrackingService` — exposed for custom controllers that send
|
|
16
|
+
transactional email outside the email-plugin pipeline.
|
|
17
|
+
- Public endpoints `/email-track/open/:id.gif` (1×1 pixel),
|
|
18
|
+
`/email-track/click/:id?u=<encoded>` (302 redirect), and
|
|
19
|
+
`/email-track/bounce` (webhook hook for DSN parsers).
|
|
20
|
+
- Admin endpoints `/email-track/log` (paginated list with filters),
|
|
21
|
+
`/email-track/log/summary` and `/email-track/log/:id`.
|
|
22
|
+
- Admin UI: standalone Email Log page + a per-customer Emails view.
|
|
23
|
+
- Licence verification via `@huloglobal/vendure-licence-sdk` with revocation
|
|
24
|
+
polling against the HULO licence server.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
HULO Global Limited — Commercial Plugin Licence
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HULO Global Limited. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is licensed, not sold, and is made available only to users
|
|
6
|
+
who hold a valid, active subscription or perpetual licence purchased
|
|
7
|
+
from HULO Global Limited.
|
|
8
|
+
|
|
9
|
+
Permitted use:
|
|
10
|
+
|
|
11
|
+
1. The software may be installed and run on a single Vendure
|
|
12
|
+
instance per active subscription (or per perpetual licence). Each
|
|
13
|
+
licence is bound to one or more hostnames named at purchase time.
|
|
14
|
+
|
|
15
|
+
2. The source code is published only so that customers may audit it
|
|
16
|
+
for security review. Modification of the source for production use
|
|
17
|
+
is permitted, but redistribution of the modified source or of any
|
|
18
|
+
derivative work — in whole or in part — is not permitted.
|
|
19
|
+
|
|
20
|
+
Prohibited use:
|
|
21
|
+
|
|
22
|
+
- Use without a valid, active licence key, except for non-production
|
|
23
|
+
evaluation under the plugin's "unlicensed mode" (which writes basic
|
|
24
|
+
delivery rows but disables the open/click endpoints).
|
|
25
|
+
- Use of the package to circumvent the licence verification routine,
|
|
26
|
+
or to extract or replace the embedded RSA public key.
|
|
27
|
+
- Resale, sublicensing, or distribution of the package outside of an
|
|
28
|
+
application that itself holds a valid HULO Vendure plugin licence.
|
|
29
|
+
|
|
30
|
+
No warranty. To the maximum extent permitted by law, HULO Global Limited
|
|
31
|
+
disclaims all warranties and all liability arising from use of this
|
|
32
|
+
software.
|
|
33
|
+
|
|
34
|
+
For commercial licensing enquiries: sales@eliteenterprisesoftware.com
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @huloglobal/vendure-plugin-email-tracking
|
|
2
|
+
|
|
3
|
+
Track delivery, opens and clicks on every transactional email a Vendure
|
|
4
|
+
server sends. Wraps `@vendure/email-plugin`, persists every send + open
|
|
5
|
+
+ click in a dedicated `email_log` table, and ships an admin UI to audit
|
|
6
|
+
the trail per customer / order / invoice.
|
|
7
|
+
|
|
8
|
+
Maintained by Wayne Garrison.
|
|
9
|
+
|
|
10
|
+
## What you get
|
|
11
|
+
|
|
12
|
+
- **Universal capture**. Every email produced by the standard
|
|
13
|
+
`@vendure/email-plugin` (order confirmation, password reset, OTP,
|
|
14
|
+
invoice, etc.) is logged automatically — no per-handler wiring.
|
|
15
|
+
- **Custom-send helper**. Inject `EmailTrackingService` into your own
|
|
16
|
+
controllers and call `sendTracked(transporter, mailOpts, meta)` to log
|
|
17
|
+
ad-hoc sends with the same engagement tracking.
|
|
18
|
+
- **Open tracking** via a 1×1 pixel served at
|
|
19
|
+
`/email-track/open/:id.gif`. First open captures IP + user-agent.
|
|
20
|
+
- **Click tracking** via a redirector at `/email-track/click/:id?u=…`.
|
|
21
|
+
Skips `mailto:`, `tel:`, `#`-anchors, and `unsubscribe`/`opt-out`
|
|
22
|
+
links (ESP rules + privacy expectations).
|
|
23
|
+
- **Per-order / per-customer / per-invoice cross-references** stored on
|
|
24
|
+
every row.
|
|
25
|
+
- **Admin UI** — global Email Log page + a per-customer Emails action
|
|
26
|
+
bar item.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yarn add @huloglobal/vendure-plugin-email-tracking
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Wire up
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { EmailPlugin } from '@vendure/email-plugin';
|
|
38
|
+
import { EmailTrackingPlugin, TrackingEmailSender } from '@huloglobal/vendure-plugin-email-tracking';
|
|
39
|
+
|
|
40
|
+
export const config: VendureConfig = {
|
|
41
|
+
plugins: [
|
|
42
|
+
EmailTrackingPlugin.init({
|
|
43
|
+
publicBaseUrl: 'https://shop.example.com',
|
|
44
|
+
licenceKey: process.env.HULO_LICENCE_KEY,
|
|
45
|
+
}),
|
|
46
|
+
EmailPlugin.init({
|
|
47
|
+
// ... your existing email-plugin config (templates, handlers, transport) ...
|
|
48
|
+
emailSender: new TrackingEmailSender(),
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Add to your admin-ui compile step:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { EmailTrackingPlugin } from '@huloglobal/vendure-plugin-email-tracking';
|
|
58
|
+
|
|
59
|
+
compileUiExtensions({
|
|
60
|
+
outputPath: 'admin-ui',
|
|
61
|
+
extensions: [EmailTrackingPlugin.uiExtensions /* + your other extensions */],
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Init options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Required | Description |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| `publicBaseUrl` | `string` | yes | Externally reachable hostname (incl. scheme) of your Vendure server. The pixel + click URLs embedded in outgoing email point here. e.g. `https://shop.example.com`. |
|
|
70
|
+
| `licenceKey` | `string` | no\* | JWT licence key issued by HULO. Without it the plugin runs in **unlicensed mode**: rows are still written, but open/click endpoints return 410 Gone and the admin UI shows an "Unlicensed" banner. |
|
|
71
|
+
| `trackedHosts` | `string[]` | no | Extra hostnames considered "ours" by the click rewriter — useful if you serve `/email-track/*` from a CDN-aliased host. |
|
|
72
|
+
|
|
73
|
+
\* **Required for production use.** Buy at
|
|
74
|
+
`https://elite-software.co.uk/licence/buy/vendure-plugin-email-tracking`.
|
|
75
|
+
|
|
76
|
+
## Behind Cloudflare / nginx / Akamai
|
|
77
|
+
|
|
78
|
+
The plugin extracts the visitor's real IP for the open-tracking pixel
|
|
79
|
+
and click-redirector from the upstream proxy's headers in this order:
|
|
80
|
+
|
|
81
|
+
1. `CF-Connecting-IP` (Cloudflare)
|
|
82
|
+
2. `True-Client-IP` (Akamai / Cloudflare Enterprise)
|
|
83
|
+
3. `X-Real-IP` (nginx default)
|
|
84
|
+
4. First entry of `X-Forwarded-For` (RFC 7239)
|
|
85
|
+
5. `req.ip` (Express socket — only useful if `app.set('trust proxy')`
|
|
86
|
+
has been set)
|
|
87
|
+
|
|
88
|
+
If you sit Vendure behind a proxy that doesn't set any of those — set
|
|
89
|
+
either `cf-connecting-ip` or `x-real-ip` on the proxy. Otherwise the
|
|
90
|
+
pixel will see the proxy's IP, not the visitor's, and `firstOpenIp`
|
|
91
|
+
will be the same value on every row.
|
|
92
|
+
|
|
93
|
+
No additional config is required on the plugin side.
|
|
94
|
+
|
|
95
|
+
## Migrations
|
|
96
|
+
|
|
97
|
+
The plugin owns the `email_log` table. Run `yarn migration:generate
|
|
98
|
+
AddEmailLog` (Vendure picks up the entity automatically) and apply with
|
|
99
|
+
`yarn migration:run`.
|
|
100
|
+
|
|
101
|
+
## Sending custom tracked emails
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { EmailTrackingService } from '@huloglobal/vendure-plugin-email-tracking';
|
|
105
|
+
|
|
106
|
+
@Controller('my-feature')
|
|
107
|
+
export class MyController {
|
|
108
|
+
constructor(private tracking: EmailTrackingService) {}
|
|
109
|
+
|
|
110
|
+
@Post('send-quote')
|
|
111
|
+
async sendQuote(/* ... */) {
|
|
112
|
+
await this.tracking.sendTracked(myNodemailerTransporter, {
|
|
113
|
+
from: '"You" <you@example.com>',
|
|
114
|
+
to: customer.email,
|
|
115
|
+
subject: 'Your quote',
|
|
116
|
+
html: '<h1>Your quote</h1><p>...</p>',
|
|
117
|
+
}, {
|
|
118
|
+
type: 'quote',
|
|
119
|
+
customerId: customer.id,
|
|
120
|
+
channelId: 1,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Licence
|
|
127
|
+
|
|
128
|
+
Commercial — see [LICENSE](./LICENSE). Requires an active subscription
|
|
129
|
+
(monthly $9.95) or a perpetual licence to run in licensed mode.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { DeepPartial, VendureEntity } from '@vendure/core';
|
|
2
|
+
export type EmailLogStatus = 'sent' | 'failed' | 'deferred' | 'bounced' | 'complained';
|
|
3
|
+
/**
|
|
4
|
+
* One row per outgoing email. Captures send metadata + engagement:
|
|
5
|
+
* - Send: who, what, when, related order/invoice/customer/application,
|
|
6
|
+
* SMTP response, smtpMessageId.
|
|
7
|
+
* - Opens: first and last timestamp, count, the IP + UA of the first
|
|
8
|
+
* open (most useful — subsequent opens are usually the same
|
|
9
|
+
* recipient or a privacy-protection prefetch).
|
|
10
|
+
* - Clicks: JSON array of click events with url + ts + ip + ua.
|
|
11
|
+
*
|
|
12
|
+
* The tracking pixel and click-redirect endpoints live at
|
|
13
|
+
* /email-track/open/:id.gif and /email-track/click/:id?u=<encoded>.
|
|
14
|
+
*/
|
|
15
|
+
export declare class EmailLog extends VendureEntity {
|
|
16
|
+
constructor(input?: DeepPartial<EmailLog>);
|
|
17
|
+
/** Logical email type: 'welcome', 'order-confirmation', 'invoice',
|
|
18
|
+
* 'credit-chase', 'credit-chase-T-7', 'payment-terms-invite',
|
|
19
|
+
* 'fraud-alert', 'gdpr-notice', 'newsletter-welcome', 'quote-request',
|
|
20
|
+
* 'otp-code', 'password-reset', 'email-verification', etc. */
|
|
21
|
+
type: string;
|
|
22
|
+
recipient: string;
|
|
23
|
+
subject: string;
|
|
24
|
+
/** Optional FROM display, captured for the audit. */
|
|
25
|
+
fromAddress: string;
|
|
26
|
+
/** BCC (almost always sales@). Captured for completeness. */
|
|
27
|
+
bcc: string;
|
|
28
|
+
/** Reply-To override (e.g. quote-request emails reply-to the buyer). */
|
|
29
|
+
replyTo: string;
|
|
30
|
+
/** Free-text context, e.g. the chase stage 'T-7' / 'due' / 'T+14'. */
|
|
31
|
+
context: string;
|
|
32
|
+
channelId: number;
|
|
33
|
+
customerId: number;
|
|
34
|
+
orderId: number;
|
|
35
|
+
orderCode: string;
|
|
36
|
+
invoiceId: number;
|
|
37
|
+
applicationId: number;
|
|
38
|
+
status: EmailLogStatus;
|
|
39
|
+
smtpResponse: string;
|
|
40
|
+
smtpMessageId: string;
|
|
41
|
+
errorMessage: string;
|
|
42
|
+
openCount: number;
|
|
43
|
+
firstOpenedAt: Date;
|
|
44
|
+
lastOpenedAt: Date;
|
|
45
|
+
firstOpenIp: string;
|
|
46
|
+
firstOpenUserAgent: string;
|
|
47
|
+
clickCount: number;
|
|
48
|
+
/** JSON-encoded array of { url, ts, ip, ua }. Capped to a sensible
|
|
49
|
+
* size; older clicks beyond the cap are summarised numerically. */
|
|
50
|
+
clicksJson: string;
|
|
51
|
+
firstClickedAt: Date;
|
|
52
|
+
/** Whether the email was sent via our "tracked" pipeline at all.
|
|
53
|
+
* Always true for emails created by the service; left as a guard
|
|
54
|
+
* for any future direct-insert tooling. */
|
|
55
|
+
tracked: boolean;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=email-log.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-log.entity.d.ts","sourceRoot":"","sources":["../src/email-log.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG3D,MAAM,MAAM,cAAc,GACpB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,SAAS,GACT,YAAY,CAAC;AAEnB;;;;;;;;;;;GAWG;AACH,qBACa,QAAS,SAAQ,aAAa;gBAC3B,KAAK,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC;IAIzC;;;kEAG8D;IAG9D,IAAI,EAAG,MAAM,CAAC;IAId,SAAS,EAAG,MAAM,CAAC;IAGnB,OAAO,EAAG,MAAM,CAAC;IAEjB,qDAAqD;IAErD,WAAW,EAAG,MAAM,CAAC;IAErB,6DAA6D;IAE7D,GAAG,EAAG,MAAM,CAAC;IAEb,wEAAwE;IAExE,OAAO,EAAG,MAAM,CAAC;IAEjB,sEAAsE;IAEtE,OAAO,EAAG,MAAM,CAAC;IAGjB,SAAS,EAAG,MAAM,CAAC;IAKnB,UAAU,EAAG,MAAM,CAAC;IAIpB,OAAO,EAAG,MAAM,CAAC;IAIjB,SAAS,EAAG,MAAM,CAAC;IAGnB,SAAS,EAAG,MAAM,CAAC;IAGnB,aAAa,EAAG,MAAM,CAAC;IAKvB,MAAM,EAAG,cAAc,CAAC;IAGxB,YAAY,EAAG,MAAM,CAAC;IAGtB,aAAa,EAAG,MAAM,CAAC;IAGvB,YAAY,EAAG,MAAM,CAAC;IAItB,SAAS,EAAG,MAAM,CAAC;IAGnB,aAAa,EAAG,IAAI,CAAC;IAGrB,YAAY,EAAG,IAAI,CAAC;IAGpB,WAAW,EAAG,MAAM,CAAC;IAGrB,kBAAkB,EAAG,MAAM,CAAC;IAG5B,UAAU,EAAG,MAAM,CAAC;IAEpB;uEACmE;IAEnE,UAAU,EAAG,MAAM,CAAC;IAGpB,cAAc,EAAG,IAAI,CAAC;IAEtB;;+CAE2C;IAE3C,OAAO,EAAG,OAAO,CAAC;CACrB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EmailLog = void 0;
|
|
13
|
+
const core_1 = require("@vendure/core");
|
|
14
|
+
const typeorm_1 = require("typeorm");
|
|
15
|
+
/**
|
|
16
|
+
* One row per outgoing email. Captures send metadata + engagement:
|
|
17
|
+
* - Send: who, what, when, related order/invoice/customer/application,
|
|
18
|
+
* SMTP response, smtpMessageId.
|
|
19
|
+
* - Opens: first and last timestamp, count, the IP + UA of the first
|
|
20
|
+
* open (most useful — subsequent opens are usually the same
|
|
21
|
+
* recipient or a privacy-protection prefetch).
|
|
22
|
+
* - Clicks: JSON array of click events with url + ts + ip + ua.
|
|
23
|
+
*
|
|
24
|
+
* The tracking pixel and click-redirect endpoints live at
|
|
25
|
+
* /email-track/open/:id.gif and /email-track/click/:id?u=<encoded>.
|
|
26
|
+
*/
|
|
27
|
+
let EmailLog = class EmailLog extends core_1.VendureEntity {
|
|
28
|
+
constructor(input) {
|
|
29
|
+
super(input);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.EmailLog = EmailLog;
|
|
33
|
+
__decorate([
|
|
34
|
+
(0, typeorm_1.Index)(),
|
|
35
|
+
(0, typeorm_1.Column)(),
|
|
36
|
+
__metadata("design:type", String)
|
|
37
|
+
], EmailLog.prototype, "type", void 0);
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, typeorm_1.Index)(),
|
|
40
|
+
(0, typeorm_1.Column)(),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], EmailLog.prototype, "recipient", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 512 }),
|
|
45
|
+
__metadata("design:type", String)
|
|
46
|
+
], EmailLog.prototype, "subject", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
(0, typeorm_1.Column)({ nullable: true, length: 512 }),
|
|
49
|
+
__metadata("design:type", String)
|
|
50
|
+
], EmailLog.prototype, "fromAddress", void 0);
|
|
51
|
+
__decorate([
|
|
52
|
+
(0, typeorm_1.Column)({ nullable: true, length: 512 }),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], EmailLog.prototype, "bcc", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, typeorm_1.Column)({ nullable: true, length: 512 }),
|
|
57
|
+
__metadata("design:type", String)
|
|
58
|
+
], EmailLog.prototype, "replyTo", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
61
|
+
__metadata("design:type", String)
|
|
62
|
+
], EmailLog.prototype, "context", void 0);
|
|
63
|
+
__decorate([
|
|
64
|
+
(0, typeorm_1.Column)({ type: 'int', default: 1 }),
|
|
65
|
+
__metadata("design:type", Number)
|
|
66
|
+
], EmailLog.prototype, "channelId", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, typeorm_1.Index)(),
|
|
69
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
70
|
+
__metadata("design:type", Number)
|
|
71
|
+
], EmailLog.prototype, "customerId", void 0);
|
|
72
|
+
__decorate([
|
|
73
|
+
(0, typeorm_1.Index)(),
|
|
74
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
75
|
+
__metadata("design:type", Number)
|
|
76
|
+
], EmailLog.prototype, "orderId", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
(0, typeorm_1.Index)(),
|
|
79
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
80
|
+
__metadata("design:type", String)
|
|
81
|
+
], EmailLog.prototype, "orderCode", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
84
|
+
__metadata("design:type", Number)
|
|
85
|
+
], EmailLog.prototype, "invoiceId", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
88
|
+
__metadata("design:type", Number)
|
|
89
|
+
], EmailLog.prototype, "applicationId", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
(0, typeorm_1.Index)(),
|
|
92
|
+
(0, typeorm_1.Column)({ type: 'varchar', default: 'sent' }),
|
|
93
|
+
__metadata("design:type", String)
|
|
94
|
+
], EmailLog.prototype, "status", void 0);
|
|
95
|
+
__decorate([
|
|
96
|
+
(0, typeorm_1.Column)({ nullable: true, length: 512 }),
|
|
97
|
+
__metadata("design:type", String)
|
|
98
|
+
], EmailLog.prototype, "smtpResponse", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
(0, typeorm_1.Column)({ nullable: true, length: 512 }),
|
|
101
|
+
__metadata("design:type", String)
|
|
102
|
+
], EmailLog.prototype, "smtpMessageId", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
|
105
|
+
__metadata("design:type", String)
|
|
106
|
+
], EmailLog.prototype, "errorMessage", void 0);
|
|
107
|
+
__decorate([
|
|
108
|
+
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
109
|
+
__metadata("design:type", Number)
|
|
110
|
+
], EmailLog.prototype, "openCount", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
(0, typeorm_1.Column)({ type: 'datetime', nullable: true }),
|
|
113
|
+
__metadata("design:type", Date)
|
|
114
|
+
], EmailLog.prototype, "firstOpenedAt", void 0);
|
|
115
|
+
__decorate([
|
|
116
|
+
(0, typeorm_1.Column)({ type: 'datetime', nullable: true }),
|
|
117
|
+
__metadata("design:type", Date)
|
|
118
|
+
], EmailLog.prototype, "lastOpenedAt", void 0);
|
|
119
|
+
__decorate([
|
|
120
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
121
|
+
__metadata("design:type", String)
|
|
122
|
+
], EmailLog.prototype, "firstOpenIp", void 0);
|
|
123
|
+
__decorate([
|
|
124
|
+
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
|
125
|
+
__metadata("design:type", String)
|
|
126
|
+
], EmailLog.prototype, "firstOpenUserAgent", void 0);
|
|
127
|
+
__decorate([
|
|
128
|
+
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
129
|
+
__metadata("design:type", Number)
|
|
130
|
+
], EmailLog.prototype, "clickCount", void 0);
|
|
131
|
+
__decorate([
|
|
132
|
+
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
|
133
|
+
__metadata("design:type", String)
|
|
134
|
+
], EmailLog.prototype, "clicksJson", void 0);
|
|
135
|
+
__decorate([
|
|
136
|
+
(0, typeorm_1.Column)({ type: 'datetime', nullable: true }),
|
|
137
|
+
__metadata("design:type", Date)
|
|
138
|
+
], EmailLog.prototype, "firstClickedAt", void 0);
|
|
139
|
+
__decorate([
|
|
140
|
+
(0, typeorm_1.Column)({ type: 'boolean', default: true }),
|
|
141
|
+
__metadata("design:type", Boolean)
|
|
142
|
+
], EmailLog.prototype, "tracked", void 0);
|
|
143
|
+
exports.EmailLog = EmailLog = __decorate([
|
|
144
|
+
(0, typeorm_1.Entity)(),
|
|
145
|
+
__metadata("design:paramtypes", [Object])
|
|
146
|
+
], EmailLog);
|
|
147
|
+
//# sourceMappingURL=email-log.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-log.entity.js","sourceRoot":"","sources":["../src/email-log.entity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { RequestContext, TransactionalConnection } from '@vendure/core';
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
import { EmailTrackingService } from './email-tracking.service';
|
|
4
|
+
export declare class EmailTrackingController {
|
|
5
|
+
private connection;
|
|
6
|
+
private tracking;
|
|
7
|
+
constructor(connection: TransactionalConnection, tracking: EmailTrackingService);
|
|
8
|
+
/**
|
|
9
|
+
* Open-tracking pixel. Returns a 1×1 transparent GIF and logs the
|
|
10
|
+
* open against the event. No-cache so privacy-protecting prefetchers
|
|
11
|
+
* still get caught per fetch (and we count opens accurately).
|
|
12
|
+
*
|
|
13
|
+
* GET /email-track/open/:id.gif
|
|
14
|
+
*/
|
|
15
|
+
open(idParam: string, req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
16
|
+
/**
|
|
17
|
+
* Click redirect. Logs the click then 302-redirects to the original
|
|
18
|
+
* URL. Returns 400 if the URL is missing or malformed (we never
|
|
19
|
+
* redirect to an empty / javascript: target).
|
|
20
|
+
*
|
|
21
|
+
* GET /email-track/click/:id?u=<encoded>
|
|
22
|
+
*/
|
|
23
|
+
click(idParam: string, u: string, req: Request, res: Response): Promise<void | Response<any, Record<string, any>>>;
|
|
24
|
+
/**
|
|
25
|
+
* Bounce webhook. Wire up a postmaster / mail-server hook (or have a
|
|
26
|
+
* scheduled DSN-parser POST here) to mark messages as bounced or
|
|
27
|
+
* complained. Both fields are required; the messageId is the
|
|
28
|
+
* `<...@domain>` value Gmail returned at submit time.
|
|
29
|
+
*
|
|
30
|
+
* POST /email-track/bounce
|
|
31
|
+
* Body: { messageId, status: 'bounced'|'complained', reason? }
|
|
32
|
+
*
|
|
33
|
+
* Currently unauthenticated to keep the integration simple — gate
|
|
34
|
+
* behind a shared secret header if you expose the endpoint to the
|
|
35
|
+
* public internet (it's fine on a private network).
|
|
36
|
+
*/
|
|
37
|
+
bounce(body: any, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
38
|
+
/**
|
|
39
|
+
* Admin: list email events, with filtering for the "Email Log" page
|
|
40
|
+
* and the per-customer Emails tab. Filterable by customerId, orderId,
|
|
41
|
+
* orderCode, status, type, recipient, and a date range.
|
|
42
|
+
*
|
|
43
|
+
* GET /email-track/log?customerId=&orderId=&status=&take=
|
|
44
|
+
*/
|
|
45
|
+
listLog(ctx: RequestContext, req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
|
|
46
|
+
/** Admin: aggregate counts by status for a quick dashboard tile. */
|
|
47
|
+
logSummary(ctx: RequestContext, req: Request, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
|
|
48
|
+
/** Admin: full detail for one event including the clicks JSON. */
|
|
49
|
+
logDetail(ctx: RequestContext, idParam: string, res: Response): Promise<Response<any, Record<string, any>> | undefined>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=email-tracking.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-tracking.controller.d.ts","sourceRoot":"","sources":["../src/email-tracking.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAA2B,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AA0BhE,qBACa,uBAAuB;IAE5B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,QAAQ;gBADR,UAAU,EAAE,uBAAuB,EACnC,QAAQ,EAAE,oBAAoB;IAG1C;;;;;;OAMG;IAEG,IAAI,CAAc,OAAO,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAalF;;;;;;OAMG;IAEG,KAAK,CAAc,OAAO,EAAE,MAAM,EAAc,CAAC,EAAE,MAAM,EAAS,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAc1G;;;;;;;;;;;;OAYG;IAEG,MAAM,CAAS,IAAI,EAAE,GAAG,EAAS,GAAG,EAAE,QAAQ;IAQpD;;;;;;OAMG;IAEG,OAAO,CAAQ,GAAG,EAAE,cAAc,EAAS,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAmCnF,oEAAoE;IAE9D,UAAU,CAAQ,GAAG,EAAE,cAAc,EAAS,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,QAAQ;IAmBtF,kEAAkE;IAE5D,SAAS,CAAQ,GAAG,EAAE,cAAc,EAAe,OAAO,EAAE,MAAM,EAAS,GAAG,EAAE,QAAQ;CAUjG"}
|